IO control on pluto appears to be working
All checks were successful
Publish Python 🐍 distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Successful in 1m27s
Publish Python 🐍 distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been skipped

This commit is contained in:
Brendan Haines 2025-03-09 01:54:05 -07:00
parent 67ddbb0f90
commit 1a76c4e7ab
2 changed files with 85 additions and 15 deletions

View File

@ -105,7 +105,7 @@ class MainWindow(QMainWindow):
prog_sweep.setMaximum(100) prog_sweep.setMaximum(100)
prog_sweep.setFormat("%v / %m") prog_sweep.setFormat("%v / %m")
# prog_sweep.setTextVisible(False) # prog_sweep.setTextVisible(False)
prog_sweep.setValue(50) prog_sweep.setValue(0)
window_layout.addWidget(prog_sweep) window_layout.addWidget(prog_sweep)
self.prog_sweep = prog_sweep self.prog_sweep = prog_sweep

View File

@ -1,11 +1,11 @@
# %% imports # %% imports
import copy import copy
from enum import IntEnum, unique
from pathlib import Path from pathlib import Path
from typing import Any, Callable, Dict, Tuple from typing import Any, Callable, Dict, Literal, Tuple
import adi import adi
import iio
# import iio
import numpy as np import numpy as np
import skrf as rf import skrf as rf
import xarray as xr import xarray as xr
@ -34,6 +34,38 @@ def generate_tone(f: float, fs: float, N: int = 1024, scale: int = 2**14):
return iq return iq
@unique
class AD9361Register(IntEnum):
AUXDAC1_WORD = 0x018
AUXDAC2_WORD = 0x019
AUXDAC1_CONFIG = 0x01A
AUXDAC2_CONFIG = 0x01B
AUXADC_CLOCK_DIVIDER = 0x01C
AUXADC_CONFIG = 0x01D
AUXADC_WORD_MSB = 0x01E
AUXADC_WORD_LSB = 0x01F
AUTO_GPIO = 0x020
AGC_GAIN_LOCK_DELAY = 0x021
AGC_ATTACK_DELAY = 0x022
AUXDAC_ENABLE_CONTROL = 0x023
RX_LOAD_SYNTH_DELAY = 0x024
TX_LOAD_SYNTH_DELAY = 0x025
EXTERNAL_LNA_CONTROL = 0x026
GPO_FORCE_AND_INIT = 0x027
GPO0_RX_DELAY = 0x028
GPO1_RX_DELAY = 0x029
GPO2_RX_DELAY = 0x02A
GPO3_RX_DELAY = 0x02B
GPO0_TX_DELAY = 0x02C
GPO1_TX_DELAY = 0x02D
GPO2_TX_DELAY = 0x02E
GPO3_TX_DELAY = 0x02F
AUXDAC1_RX_DELAY = 0x030
AUXDAC1_TX_DELAY = 0x031
AUXDAC2_RX_DELAY = 0x032
AUXDAC2_TX_DELAY = 0x033
class Charon: class Charon:
FREQUENCY_OFFSET = 1e6 FREQUENCY_OFFSET = 1e6
@ -49,7 +81,8 @@ class Charon:
self.frequency = frequency self.frequency = frequency
# everything RF # everything RF
self.sdr = adi.ad9361(uri=f"ip:{ip}") uri = f"ip:{ip}"
self.sdr = adi.ad9361(uri=uri)
for attr, expected in [ for attr, expected in [
("adi,2rx-2tx-mode-enable", True), ("adi,2rx-2tx-mode-enable", True),
# ("adi,gpo-manual-mode-enable", True), # ("adi,gpo-manual-mode-enable", True),
@ -81,14 +114,16 @@ class Charon:
self.sdr.tx_hardwaregain_chan0 = -10 self.sdr.tx_hardwaregain_chan0 = -10
# # switch control # # switch control
# ctx = iio.Context(uri) ctx = iio.Context(uri)
# self.ctrl = ctx.find_device("ad9361-phy") self.ctrl = ctx.find_device("ad9361-phy")
# # raw ad9361 register accesss: # raw ad9361 register accesss:
# # https://ez.analog.com/linux-software-drivers/f/q-a/120853/control-fmcomms3-s-gpo-with-python # https://ez.analog.com/linux-software-drivers/f/q-a/120853/control-fmcomms3-s-gpo-with-python
# # https://www.analog.com/media/cn/technical-documentation/user-guides/ad9364_register_map_reference_manual_ug-672.pdf # noqa: E501 # https://www.analog.com/media/cn/technical-documentation/user-guides/ad9364_register_map_reference_manual_ug-672.pdf # noqa: E501
# self.ctrl.reg_write(0x26, 0x90) # bit 7: AuxDAC Manual, bit 4: GPO Manual self.ctrl.reg_write(AD9361Register.EXTERNAL_LNA_CONTROL, 0x90) # bit 7: AuxDAC Manual, bit 4: GPO Manual
# self._set_gpo(self.ports[0] - 1) self.ctrl.reg_write(AD9361Register.AUXDAC_ENABLE_CONTROL, 0x3F)
# # TODO: init AuxDAC self._set_gpo(0b0000)
self._set_dac(value=0, channel=1)
self._set_dac(value=0, channel=2)
def get_config(self) -> Dict[str, Any]: def get_config(self) -> Dict[str, Any]:
config = dict() config = dict()
@ -113,10 +148,45 @@ class Charon:
return config return config
def _get_gpo(self) -> int: def _get_gpo(self) -> int:
return (self.ctrl.reg_read(0x27) >> 4) & 0x0F return (self.ctrl.reg_read(AD9361Register.GPO_FORCE_AND_INIT) >> 4) & 0x0F
def _set_gpo(self, value: int) -> None: def _set_gpo(self, value: int) -> None:
self.ctrl.reg_write(0x27, (value & 0x0F) << 4) # bits 7-4: GPO3-0 self.ctrl.reg_write(AD9361Register.GPO_FORCE_AND_INIT, (value & 0x0F) << 4) # bits 7-4: GPO3-0
def _set_dac(self, value: int, channel: Literal[1, 2]):
if channel not in [1, 2]:
raise ValueError(f"Invalid channel {channel}. Must be 1 or 2")
if value > 0x3FF or value < 0:
raise ValueError("Invalid value for 10 bit DAC. Must be between 0 and 0x3FF (inclusive)")
@unique
class Vref(IntEnum):
VREF_1V0 = 0b00
VREF_1V5 = 0b01
VREF_2V0 = 0b10
VREF_2V5 = 0b11
@unique
class StepFactor(IntEnum):
FACTOR_2 = 0b0
FACTOR_1 = 0b1
# https://www.analog.com/media/cn/technical-documentation/user-guides/ad9364_register_map_reference_manual_ug-672.pdf
# page 13
# vout = 0.97 * vref + (0.000738 + 9e-6 * (vref * 1.6 - 2)) * auxdac_word[9:0] * step_factor - 0.3572 * step_factor + 0.05
# vout ~= (vref - 0.3572 * step_factor) + 0.000738 * auxdac_word[9:0] * step_factor
# which gives a 1.5V swing with step_factor == 2 and 0.75V swing with step_factor == 1
# vref basically just changes the minimum voltage with negligible impact on output scaling
self.ctrl.reg_write(
AD9361Register.__getitem__(f"AUXDAC{channel}_WORD"),
(value >> 2) & 0xFF,
)
self.ctrl.reg_write(
AD9361Register.__getitem__(f"AUXDAC{channel}_CONFIG"),
(value & 0x3) | (Vref.VREF_1V0.value << 2) | (StepFactor.FACTOR_2 << 4),
)
def set_output_power(self, power: float): def set_output_power(self, power: float):
# FIXME: this is a hack because I don't want to go through re-calibration # FIXME: this is a hack because I don't want to go through re-calibration