Skip to content

Commit e48f7e3

Browse files
committed
adi: admfm8000: initial helper class for FMCW TX board
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
1 parent 42f7d71 commit e48f7e3

File tree

6 files changed

+623
-0
lines changed

6 files changed

+623
-0
lines changed

adi/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
from adi.adis16550 import adis16550
9191
from adi.adl5240 import adl5240
9292
from adi.adl5960 import adl5960
93+
from adi.admfm8000 import admfm8000
9394
from adi.admv8818 import admv8818
9495
from adi.adpd188 import adpd188
9596
from adi.adpd410x import adpd410x

adi/admfm8000.py

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
# Copyright (C) 2026 Analog Devices, Inc.
2+
#
3+
# SPDX short identifier: ADIBSD
4+
5+
import warnings
6+
7+
from adi.ad9910 import ad9910
8+
from adi.adf41513 import adf41513
9+
from adi.adrf5720 import adrf5720
10+
11+
12+
class admfm8000:
13+
"""ADMFM8000 FMCW Transmitter
14+
15+
This class provides a high-level interface for controlling the ADMFM8000.
16+
The system consists of an AD9910 DDS, an ADF41513 PLL, and an ADRF5720 DSA.
17+
Helper functions to configure the DDS modes have the frequency values
18+
automatically scaled down based on the PLL reference input frequency and the
19+
desired VCO output frequency.
20+
"""
21+
22+
def __init__(self, uri="", vco_feedback_div=16, pll_refin_frequency=50e6):
23+
self.dds = ad9910(uri)
24+
self.pll = adf41513(uri)
25+
self.dsa = adrf5720(uri, "adrf5730")
26+
self._pll_refin_init_frequency = pll_refin_frequency
27+
self._pll_refin_frequency = pll_refin_frequency
28+
self._vco_feedback_div = vco_feedback_div
29+
self.vco_frequency = 25.6e9
30+
31+
@property
32+
def attenuation(self):
33+
"""Get/Set the DSA attenuation in dB"""
34+
return self.dsa.attenuation
35+
36+
@attenuation.setter
37+
def attenuation(self, value):
38+
self.dsa.attenuation = value
39+
40+
@property
41+
def pll_refin_frequency(self):
42+
"""Get/Set the PLL reference input frequency in Hz
43+
44+
This value should represent the center frequency of the signal generated
45+
by the DDS that is fed into the PLL reference input.
46+
"""
47+
return self._pll_refin_frequency
48+
49+
@pll_refin_frequency.setter
50+
def pll_refin_frequency(self, value):
51+
if value > 80e6 or value < 10e6:
52+
raise ValueError("Invalid PLL RefIn frequency")
53+
self._pll_refin_frequency = value
54+
self.vco_frequency = self._vco_frequency
55+
56+
@property
57+
def vco_frequency(self):
58+
"""Get/Set the VCO frequency output value in Hz
59+
60+
This value should represent the center frequency of the signal generated
61+
by the external VCO. It takes into account the current PLL settings and
62+
the VCO feedback divider.
63+
"""
64+
return self._vco_frequency
65+
66+
@vco_frequency.setter
67+
def vco_frequency(self, value):
68+
pll_multiplier = (value / self._vco_feedback_div) / self._pll_refin_frequency
69+
self.pll.frequency = pll_multiplier * self._pll_refin_init_frequency
70+
self._vco_frequency = value
71+
72+
@property
73+
def frequency_scaling_factor(self):
74+
"""Get the overall frequency scaling factor based on current PLL and VCO
75+
settings
76+
77+
This value takes into account the PLL settings and the feedback divider
78+
to calculate the effective frequency scaling factor.
79+
"""
80+
return self._vco_frequency / self._pll_refin_frequency
81+
82+
def single_tone_config(self, profile=0, frequency=None, scale=None, phase=None):
83+
"""Configure the DDS for single tone output with respect to VCO frequency output.
84+
85+
:param profile: DDS Profile number
86+
:param frequency: Output frequency in Hz
87+
:param scale: Output scale (0.0 to 1.0)
88+
:param phase: Output phase in radians
89+
"""
90+
self.dds.profile = profile
91+
if frequency is not None:
92+
self.dds.st.profiles[profile].frequency = (
93+
frequency / self.frequency_scaling_factor
94+
)
95+
if phase is not None:
96+
self.dds.st.profiles[profile].phase = phase
97+
if scale is not None:
98+
self.dds.st.profiles[profile].scale = scale
99+
100+
def parallel_port_config(
101+
self, enable=True, frequency_np=None, cyclic=True, rate=None
102+
):
103+
"""Configure the DDS Parallel Port channel
104+
105+
:param enable: Enable/Disable the Parallel Port channel
106+
:param frequency_np: numpy array of frequency values in Hz
107+
:param cyclic: Enable/Disable cyclic mode for the DMA buffer
108+
:param rate: Rate of the Parallel Port channel in samples/second
109+
"""
110+
if enable:
111+
self.dds.parallel_port.enable = 1
112+
else:
113+
self.dds.tx_destroy_buffer()
114+
self.dds.parallel_port.enable = 0
115+
116+
if rate is not None:
117+
try:
118+
self.dds.parallel_port.rate = rate
119+
except Exception as ex:
120+
warnings.warn(f"Failed to set Parallel Port rate: {ex}")
121+
122+
if frequency_np is not None:
123+
self.dds.parallel_port.frequency_push(
124+
frequency_np / self.frequency_scaling_factor, cyclic=cyclic
125+
)
126+
127+
def digital_ramp_config(
128+
self,
129+
enable=None,
130+
mode=None,
131+
freq_min=None,
132+
freq_max=None,
133+
inc_step=None,
134+
dec_step=None,
135+
inc_rate=None,
136+
dec_rate=None,
137+
inc_ramp_time=None,
138+
dec_ramp_time=None,
139+
**kwargs,
140+
):
141+
"""Configure the DDS Digital Ramp Generator (DRG)
142+
143+
:param enable: Enable/Disable the Digital Ramp Generator
144+
:param mode: DRG Operating Mode as defined in ad9910.digital_ramp_generator.mode
145+
:param freq_min: Minimum frequency in Hz
146+
:param freq_max: Maximum frequency in Hz
147+
:param inc_step: Increment step in Hz
148+
:param dec_step: Decrement step in Hz
149+
:param inc_rate: Increment rate in samples/second
150+
:param dec_rate: Decrement rate in samples/second
151+
:param inc_ramp_time: Ramp time for increment in seconds
152+
:param dec_ramp_time: Ramp time for decrement in seconds
153+
:param kwargs: Additional DRG attributes to set as key-value pairs
154+
"""
155+
if enable:
156+
# disable DRG when updating settings.
157+
# This ensures that HDL starts from a known state.
158+
self.dds.drg.enable = 0
159+
160+
self.dds.drg.destination = ad9910.destination.FREQUENCY
161+
162+
for attr, value in kwargs.items():
163+
try:
164+
self.dds.drg._set_iio_attr(attr, value)
165+
except Exception as ex:
166+
warnings.warn(f"Failed to set attribute {attr} on DRG channel: {ex}")
167+
168+
if freq_min is not None:
169+
self.dds.drg.frequency.min = freq_min / self.frequency_scaling_factor
170+
else:
171+
freq_min = self.dds.drg.frequency.min * self.frequency_scaling_factor
172+
173+
if freq_max is not None:
174+
self.dds.drg.frequency.max = freq_max / self.frequency_scaling_factor
175+
else:
176+
freq_max = self.dds.drg.frequency.max * self.frequency_scaling_factor
177+
178+
if mode is not None:
179+
self.dds.drg.operating_mode = mode
180+
181+
if inc_step is None and inc_rate is None and inc_ramp_time is not None:
182+
inc_rate = self.dds.sysclk_frequency / 4 # max DRG update rate
183+
inc_step = (freq_max - freq_min) / (inc_rate * inc_ramp_time)
184+
185+
if dec_step is None and dec_rate is None and dec_ramp_time is not None:
186+
dec_rate = self.dds.sysclk_frequency / 4 # max DRG update rate
187+
dec_step = (freq_max - freq_min) / (dec_rate * dec_ramp_time)
188+
189+
if inc_step is not None:
190+
self.dds.drg.frequency.increment = inc_step / self.frequency_scaling_factor
191+
if dec_step is not None:
192+
self.dds.drg.frequency.decrement = dec_step / self.frequency_scaling_factor
193+
if inc_rate is not None:
194+
self.dds.drg.increment_rate = inc_rate
195+
if dec_rate is not None:
196+
self.dds.drg.decrement_rate = dec_rate
197+
if enable is not None:
198+
self.dds.drg.enable = 1 if enable else 0
199+
200+
def ram_control_profile_config(
201+
self, profile=0, mode=None, addr_range=None, rate=None
202+
):
203+
"""Configure the DDS RAM Control Profiles
204+
205+
:param profile: RAM Control Profile number
206+
:param mode: RAM Control Mode as defined in ad9910.ram_control.mode
207+
:param addr_range: RAM address range as a tuple (start_addr, end_addr)
208+
:param rate: RAM update rate in samples/second
209+
"""
210+
self.dds.profile = profile
211+
if isinstance(addr_range, tuple) and len(addr_range) == 2:
212+
self.dds.ram.profiles[profile].address_range = addr_range
213+
if mode is not None:
214+
self.dds.ram.profiles[profile].operating_mode = mode
215+
if rate is not None:
216+
self.dds.ram.profiles[profile].rate = rate
217+
218+
def ram_control_config(self, enable=True, frequency_np=None):
219+
"""Load frequency list with respect to VCO frequency output into DDS RAM in Hz
220+
221+
:param enable: Enable/Disable the RAM Mode
222+
:param frequency_np: numpy array of frequency values in Hz
223+
"""
224+
if frequency_np is not None:
225+
if self.dds.ram.enable == 1:
226+
# make sure RAM is disabled before loading new values
227+
self.dds.ram.enable = 0
228+
229+
self.dds.ram.destination = ad9910.destination.FREQUENCY
230+
self.dds.ram.frequency_load(frequency_np / self.frequency_scaling_factor)
231+
232+
self.dds.ram.enable = 1 if enable else 0
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
adi.admfm8000 module
2+
====================
3+
4+
.. automodule:: adi.admfm8000
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:

doc/source/devices/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ Supported Devices
9696
adi.adis16550
9797
adi.adl5240
9898
adi.adl5960
99+
adi.admfm8000
99100
adi.admv8818
100101
adi.adpd1080
101102
adi.adpd188

0 commit comments

Comments
 (0)