forked from M-Labs/artiq
ad9914: port to NAC3
This commit is contained in:
parent
0266d52497
commit
bf8e188868
@ -2,13 +2,18 @@
|
|||||||
Driver for the AD9914 DDS (with parallel bus) on RTIO.
|
Driver for the AD9914 DDS (with parallel bus) on RTIO.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from numpy import int32, int64
|
||||||
|
|
||||||
from artiq.language.core import *
|
from artiq.language.core import *
|
||||||
from artiq.language.types import *
|
|
||||||
from artiq.language.units import *
|
from artiq.language.units import *
|
||||||
from artiq.coredevice.rtio import rtio_output
|
from artiq.coredevice.rtio import rtio_output
|
||||||
|
from artiq.coredevice.core import Core
|
||||||
|
|
||||||
from numpy import int32, int64
|
|
||||||
|
# NAC3TODO work around https://git.m-labs.hk/M-Labs/nac3/issues/189
|
||||||
|
@nac3
|
||||||
|
class ValueError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@ -43,6 +48,7 @@ AD9914_FUD = 0x80
|
|||||||
AD9914_GPIO = 0x81
|
AD9914_GPIO = 0x81
|
||||||
|
|
||||||
|
|
||||||
|
@nac3
|
||||||
class AD9914:
|
class AD9914:
|
||||||
"""Driver for one AD9914 DDS channel.
|
"""Driver for one AD9914 DDS channel.
|
||||||
|
|
||||||
@ -57,10 +63,21 @@ class AD9914:
|
|||||||
:param channel: channel number (on the bus) of the DDS device to control.
|
:param channel: channel number (on the bus) of the DDS device to control.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kernel_invariants = {"core", "sysclk", "bus_channel", "channel",
|
core: KernelInvariant[Core]
|
||||||
"rtio_period_mu", "sysclk_per_mu", "write_duration_mu",
|
sysclk: KernelInvariant[float]
|
||||||
"dac_cal_duration_mu", "init_duration_mu", "init_sync_duration_mu",
|
bus_channel: KernelInvariant[int32]
|
||||||
"set_duration_mu", "set_x_duration_mu", "exit_x_duration_mu"}
|
channel: KernelInvariant[int32]
|
||||||
|
phase_mode: Kernel[int32]
|
||||||
|
rtio_period_mu: KernelInvariant[int64]
|
||||||
|
sysclk_per_mu: KernelInvariant[int64]
|
||||||
|
write_duration_mu: KernelInvariant[int64]
|
||||||
|
dac_cal_duration_mu: KernelInvariant[int64]
|
||||||
|
init_duration_mu: KernelInvariant[int64]
|
||||||
|
init_sync_duration_mu: KernelInvariant[int64]
|
||||||
|
set_duration_mu: KernelInvariant[int64]
|
||||||
|
set_x_duration_mu: KernelInvariant[int64]
|
||||||
|
exit_x_duration_mu: KernelInvariant[int64]
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, dmgr, sysclk, bus_channel, channel, core_device="core"):
|
def __init__(self, dmgr, sysclk, bus_channel, channel, core_device="core"):
|
||||||
self.core = dmgr.get(core_device)
|
self.core = dmgr.get(core_device)
|
||||||
@ -70,7 +87,7 @@ class AD9914:
|
|||||||
self.phase_mode = PHASE_MODE_CONTINUOUS
|
self.phase_mode = PHASE_MODE_CONTINUOUS
|
||||||
|
|
||||||
self.rtio_period_mu = int64(8)
|
self.rtio_period_mu = int64(8)
|
||||||
self.sysclk_per_mu = int32(self.sysclk * self.core.ref_period)
|
self.sysclk_per_mu = int64(self.sysclk * self.core.ref_period)
|
||||||
|
|
||||||
self.write_duration_mu = 5 * self.rtio_period_mu
|
self.write_duration_mu = 5 * self.rtio_period_mu
|
||||||
self.dac_cal_duration_mu = 147000 * self.rtio_period_mu
|
self.dac_cal_duration_mu = 147000 * self.rtio_period_mu
|
||||||
@ -81,7 +98,7 @@ class AD9914:
|
|||||||
self.exit_x_duration_mu = 3 * self.write_duration_mu
|
self.exit_x_duration_mu = 3 * self.write_duration_mu
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write(self, addr, data):
|
def write(self, addr: int32, data: int32):
|
||||||
rtio_output((self.bus_channel << 8) | addr, data)
|
rtio_output((self.bus_channel << 8) | addr, data)
|
||||||
delay_mu(self.write_duration_mu)
|
delay_mu(self.write_duration_mu)
|
||||||
|
|
||||||
@ -113,7 +130,7 @@ class AD9914:
|
|||||||
self.write(AD9914_FUD, 0)
|
self.write(AD9914_FUD, 0)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def init_sync(self, sync_delay):
|
def init_sync(self, sync_delay: int32):
|
||||||
"""Resets and initializes the DDS channel as well as configures
|
"""Resets and initializes the DDS channel as well as configures
|
||||||
the AD9914 DDS for synchronisation. The synchronisation procedure
|
the AD9914 DDS for synchronisation. The synchronisation procedure
|
||||||
follows the steps outlined in the AN-1254 application note.
|
follows the steps outlined in the AN-1254 application note.
|
||||||
@ -157,7 +174,7 @@ class AD9914:
|
|||||||
self.write(AD9914_FUD, 0)
|
self.write(AD9914_FUD, 0)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_phase_mode(self, phase_mode):
|
def set_phase_mode(self, phase_mode: int32):
|
||||||
"""Sets the phase mode of the DDS channel. Supported phase modes are:
|
"""Sets the phase mode of the DDS channel. Supported phase modes are:
|
||||||
|
|
||||||
* :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged when
|
* :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged when
|
||||||
@ -181,8 +198,8 @@ class AD9914:
|
|||||||
self.phase_mode = phase_mode
|
self.phase_mode = phase_mode
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_mu(self, ftw, pow=0, phase_mode=_PHASE_MODE_DEFAULT,
|
def set_mu(self, ftw: int32, pow: int32 = 0, phase_mode: int32 = _PHASE_MODE_DEFAULT,
|
||||||
asf=0x0fff, ref_time_mu=-1):
|
asf: int32 = 0x0fff, ref_time_mu: int64 = int64(-1)) -> int32:
|
||||||
"""Sets the DDS channel to the specified frequency and phase.
|
"""Sets the DDS channel to the specified frequency and phase.
|
||||||
|
|
||||||
This uses machine units (FTW and POW). The frequency tuning word width
|
This uses machine units (FTW and POW). The frequency tuning word width
|
||||||
@ -205,7 +222,7 @@ class AD9914:
|
|||||||
"""
|
"""
|
||||||
if phase_mode == _PHASE_MODE_DEFAULT:
|
if phase_mode == _PHASE_MODE_DEFAULT:
|
||||||
phase_mode = self.phase_mode
|
phase_mode = self.phase_mode
|
||||||
if ref_time_mu < 0:
|
if ref_time_mu < int64(0):
|
||||||
ref_time_mu = now_mu()
|
ref_time_mu = now_mu()
|
||||||
delay_mu(-self.set_duration_mu)
|
delay_mu(-self.set_duration_mu)
|
||||||
|
|
||||||
@ -224,60 +241,60 @@ class AD9914:
|
|||||||
# Clear phase accumulator on FUD
|
# Clear phase accumulator on FUD
|
||||||
# Enable autoclear phase accumulator and enables OSK.
|
# Enable autoclear phase accumulator and enables OSK.
|
||||||
self.write(AD9914_REG_CFR1L, 0x2108)
|
self.write(AD9914_REG_CFR1L, 0x2108)
|
||||||
fud_time = now_mu() + 2 * self.write_duration_mu
|
fud_time = now_mu() + int64(2) * self.write_duration_mu
|
||||||
pow -= int32((ref_time_mu - fud_time) * self.sysclk_per_mu * ftw >> (32 - 16))
|
pow -= int32((ref_time_mu - fud_time) * self.sysclk_per_mu * int64(ftw) >> int64(32 - 16))
|
||||||
if phase_mode == PHASE_MODE_TRACKING:
|
if phase_mode == PHASE_MODE_TRACKING:
|
||||||
pow += int32(ref_time_mu * self.sysclk_per_mu * ftw >> (32 - 16))
|
pow += int32(ref_time_mu * self.sysclk_per_mu * int64(ftw) >> int64(32 - 16))
|
||||||
|
|
||||||
self.write(AD9914_REG_POW, pow)
|
self.write(AD9914_REG_POW, pow)
|
||||||
self.write(AD9914_REG_ASF, asf)
|
self.write(AD9914_REG_ASF, asf)
|
||||||
self.write(AD9914_FUD, 0)
|
self.write(AD9914_FUD, 0)
|
||||||
return pow
|
return pow
|
||||||
|
|
||||||
@portable(flags={"fast-math"})
|
@portable
|
||||||
def frequency_to_ftw(self, frequency):
|
def frequency_to_ftw(self, frequency: float) -> int32:
|
||||||
"""Returns the 32-bit frequency tuning word corresponding to the given
|
"""Returns the 32-bit frequency tuning word corresponding to the given
|
||||||
frequency.
|
frequency.
|
||||||
"""
|
"""
|
||||||
return int32(round(float(int64(2)**32*frequency/self.sysclk)))
|
return round(float(int64(2)**int64(32))*frequency/self.sysclk)
|
||||||
|
|
||||||
@portable(flags={"fast-math"})
|
@portable
|
||||||
def ftw_to_frequency(self, ftw):
|
def ftw_to_frequency(self, ftw: int32) -> float:
|
||||||
"""Returns the frequency corresponding to the given frequency tuning
|
"""Returns the frequency corresponding to the given frequency tuning
|
||||||
word.
|
word.
|
||||||
"""
|
"""
|
||||||
return ftw*self.sysclk/int64(2)**32
|
return float(ftw)*self.sysclk/float(int64(2)**int64(32))
|
||||||
|
|
||||||
@portable(flags={"fast-math"})
|
@portable
|
||||||
def turns_to_pow(self, turns):
|
def turns_to_pow(self, turns: float) -> int32:
|
||||||
"""Returns the 16-bit phase offset word corresponding to the given
|
"""Returns the 16-bit phase offset word corresponding to the given
|
||||||
phase in turns."""
|
phase in turns."""
|
||||||
return round(float(turns*2**16)) & 0xffff
|
return round(float(turns*float(2**16))) & 0xffff
|
||||||
|
|
||||||
@portable(flags={"fast-math"})
|
@portable
|
||||||
def pow_to_turns(self, pow):
|
def pow_to_turns(self, pow: int32) -> float:
|
||||||
"""Returns the phase in turns corresponding to the given phase offset
|
"""Returns the phase in turns corresponding to the given phase offset
|
||||||
word."""
|
word."""
|
||||||
return pow/2**16
|
return float(pow)/float(2**16)
|
||||||
|
|
||||||
@portable(flags={"fast-math"})
|
@portable
|
||||||
def amplitude_to_asf(self, amplitude):
|
def amplitude_to_asf(self, amplitude: float) -> int32:
|
||||||
"""Returns 12-bit amplitude scale factor corresponding to given
|
"""Returns 12-bit amplitude scale factor corresponding to given
|
||||||
amplitude."""
|
amplitude."""
|
||||||
code = round(float(amplitude * 0x0fff))
|
code = round(float(amplitude * float(0x0fff)))
|
||||||
if code < 0 or code > 0xfff:
|
if code < 0 or code > 0xfff:
|
||||||
raise ValueError("Invalid AD9914 amplitude!")
|
raise ValueError("Invalid AD9914 amplitude!")
|
||||||
return code
|
return code
|
||||||
|
|
||||||
@portable(flags={"fast-math"})
|
@portable
|
||||||
def asf_to_amplitude(self, asf):
|
def asf_to_amplitude(self, asf: int32) -> float:
|
||||||
"""Returns the amplitude corresponding to the given amplitude scale
|
"""Returns the amplitude corresponding to the given amplitude scale
|
||||||
factor."""
|
factor."""
|
||||||
return asf/0x0fff
|
return asf/0x0fff
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set(self, frequency, phase=0.0, phase_mode=_PHASE_MODE_DEFAULT,
|
def set(self, frequency: float, phase: float = 0.0, phase_mode: int32 = _PHASE_MODE_DEFAULT,
|
||||||
amplitude=1.0):
|
amplitude: float = 1.0) -> float:
|
||||||
"""Like :meth:`set_mu`, but uses Hz and turns."""
|
"""Like :meth:`set_mu`, but uses Hz and turns."""
|
||||||
return self.pow_to_turns(
|
return self.pow_to_turns(
|
||||||
self.set_mu(self.frequency_to_ftw(frequency),
|
self.set_mu(self.frequency_to_ftw(frequency),
|
||||||
@ -286,7 +303,7 @@ class AD9914:
|
|||||||
|
|
||||||
# Extended-resolution functions
|
# Extended-resolution functions
|
||||||
@kernel
|
@kernel
|
||||||
def set_x_mu(self, xftw, amplitude=0x0fff):
|
def set_x_mu(self, xftw: int64, amplitude: int32 = 0x0fff):
|
||||||
"""Set the DDS frequency and amplitude with an extended-resolution
|
"""Set the DDS frequency and amplitude with an extended-resolution
|
||||||
(63-bit) frequency tuning word.
|
(63-bit) frequency tuning word.
|
||||||
|
|
||||||
@ -300,10 +317,10 @@ class AD9914:
|
|||||||
|
|
||||||
self.write(AD9914_GPIO, (1 << self.channel) << 1)
|
self.write(AD9914_GPIO, (1 << self.channel) << 1)
|
||||||
|
|
||||||
self.write(AD9914_REG_DRGAL, xftw & 0xffff)
|
self.write(AD9914_REG_DRGAL, int32(xftw) & 0xffff)
|
||||||
self.write(AD9914_REG_DRGAH, (xftw >> 16) & 0x7fff)
|
self.write(AD9914_REG_DRGAH, int32(xftw >> int64(16)) & 0x7fff)
|
||||||
self.write(AD9914_REG_DRGFL, (xftw >> 31) & 0xffff)
|
self.write(AD9914_REG_DRGFL, int32(xftw >> int64(31)) & 0xffff)
|
||||||
self.write(AD9914_REG_DRGFH, (xftw >> 47) & 0xffff)
|
self.write(AD9914_REG_DRGFH, int32(xftw >> int64(47)) & 0xffff)
|
||||||
self.write(AD9914_REG_ASF, amplitude)
|
self.write(AD9914_REG_ASF, amplitude)
|
||||||
|
|
||||||
self.write(AD9914_FUD, 0)
|
self.write(AD9914_FUD, 0)
|
||||||
@ -316,23 +333,23 @@ class AD9914:
|
|||||||
self.write(AD9914_REG_DRGAL, 0)
|
self.write(AD9914_REG_DRGAL, 0)
|
||||||
self.write(AD9914_REG_DRGAH, 0)
|
self.write(AD9914_REG_DRGAH, 0)
|
||||||
|
|
||||||
@portable(flags={"fast-math"})
|
@portable
|
||||||
def frequency_to_xftw(self, frequency):
|
def frequency_to_xftw(self, frequency: float) -> int64:
|
||||||
"""Returns the 63-bit frequency tuning word corresponding to the given
|
"""Returns the 63-bit frequency tuning word corresponding to the given
|
||||||
frequency (extended resolution mode).
|
frequency (extended resolution mode).
|
||||||
"""
|
"""
|
||||||
return int64(round(2.0*float(int64(2)**62)*frequency/self.sysclk)) & (
|
return round64(2.0*float(int64(2)**int64(62))*frequency/self.sysclk) & (
|
||||||
(int64(1) << 63) - 1)
|
(int64(1) << int64(63)) - int64(1))
|
||||||
|
|
||||||
@portable(flags={"fast-math"})
|
@portable
|
||||||
def xftw_to_frequency(self, xftw):
|
def xftw_to_frequency(self, xftw: int64) -> float:
|
||||||
"""Returns the frequency corresponding to the given frequency tuning
|
"""Returns the frequency corresponding to the given frequency tuning
|
||||||
word (extended resolution mode).
|
word (extended resolution mode).
|
||||||
"""
|
"""
|
||||||
return xftw*self.sysclk/(2.0*float(int64(2)**62))
|
return float(xftw)*self.sysclk/(2.0*float(int64(2)**int64(62)))
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_x(self, frequency, amplitude=1.0):
|
def set_x(self, frequency: float, amplitude: float = 1.0):
|
||||||
"""Like :meth:`set_x_mu`, but uses Hz and turns.
|
"""Like :meth:`set_x_mu`, but uses Hz and turns.
|
||||||
|
|
||||||
Note that the precision of ``float`` is less than the precision
|
Note that the precision of ``float`` is less than the precision
|
||||||
|
Loading…
Reference in New Issue
Block a user