Red Pitaya RF Front End
Note that this page is automatically generated from an org-mode source file. The Python code for this reference design is also automatically generated from this file using org-babel-tangle.
The Script
Run the resulting scripts as follows:
Frontend hardware address.
Frontend hardware configuration format.
The main
Function
The asyncio
framework is used to coordinate communications over the
serial device without blocking user interface events. For this
purpose we make use of QEventLoop
and QThreadExecutor
from the
qasync package.
The event loop is equipped with an instance of QThreadExecutor
which
executes a single thread. The idea here is to ensure that requests to
the ECApp serial device will execute one by one in a manner which will
not block the operation of the Qt event loop.
def main(): <<process-cmdline-args>> # If specified, read HW config from file hw_config = None if args.frontend_config is not None: try: with open(args.frontend_config) as fd: hw_config = json.load(fd) except FileNotFoundError: print(f'No such file "{args.frontend_config}" for front end HW config.') print('Using default front end HW config. instead.') # This ensures that Cntl-C will work as expected: signal.signal(signal.SIGINT, signal.SIG_DFL) app = QCoreApplication(sys.argv) loop = QEventLoop(app) loop.set_default_executor(QThreadExecutor(1)) asyncio.set_event_loop(loop) if hw_config is not None: fe_app = FrontEndApp( hw_config=hw_config['hw_config'], app_config=hw_config['app_config'] ) else: fe_app = FrontEndApp() if hw_config is not None and 'capabilities' in hw_config: front_end = FrontEndService( fe_app, serial_device=args.device, baudrate=args.baudrate, capabilities=hw_config['capabilities'] ) else: front_end = FrontEndService( fe_app, serial_device=args.device, baudrate=args.baudrate ) server_thread = QThread() server = RPyCServer( front_end, args.ipaddr, args.port) server.moveToThread(server_thread) server_thread.started.connect(server.run) server.finished.connect(server_thread.quit) server.finished.connect(server.deleteLater) server_thread.finished.connect(server_thread.deleteLater) server_thread.start() try: loop.set_debug(True) loop.run_forever() except KeyboardInterrupt: pass loop.close()
Process command line arguments
defaultBaud = 0 parser = ArgumentParser(description="Red Pitaya RF Front End") parser.add_argument("-d", "--device", default=None, required=True, help="The front end hardware serial device") parser.add_argument("-b", "--baudrate", default=defaultBaud, type=int, help="Baud rate (default: {})".format(defaultBaud)) parser.add_argument("-A", "--ipaddr", default="127.0.0.1", help="IP address for the RPyC server instance") parser.add_argument("-P", "--port", default=18870, type=int, help="TCP port for the RPyC server instance") parser.add_argument("-F", "--frontend_config", default=None, help="Frontend hardware configuration.") args = parser.parse_args()
ADC Channel Class
from typing import ( List, Dict, Callable ) import serial from scipy import interpolate from rfblocks import ( pe43711, hmc833, PE43711Controller, HMC833Controller ) class ADCChan(object): def __init__(self, chan_id: str, hw_config: Dict, chan_config: Dict) -> None: """ """ self._chan_id: str = chan_id self._freq_resp_cal_data: Dict[float, float] = {} self._noise_floor_cal_data: Dict[float, float] = {} self._stepatten_controller = None self._lo1_controller = None # self._lo2_controller = None if 'stepatten' in hw_config: self._stepatten_controller = PE43711Controller( f'stepatten_{chan_id[-1]}', pe43711(**hw_config['stepatten'])) self._stepatten_controller.attenuation = chan_config['stepatten']['atten'] if 'lo1' in hw_config: self._lo1_controller = HMC833Controller( f'lo1_{chan_id[-1]}', hmc833(**hw_config['lo1']), chan_config['lo1']) @property def stepatten_ctl(self) -> PE43711Controller: """Return a reference to the channel's front end step attenuator controller. """ return self._stepatten_controller @property def lo1_synth_ctl(self) -> HMC833Controller: """Return a reference to the channel's LO1 synthesizer controller. """ return self._lo1_controller @property def freq_resp_cal_data(self) -> Dict[float, float]: return self._freq_resp_cal_data @freq_resp_cal_data.setter def freq_resp_cal_data(self, fr: Dict[float, float]) -> None: self._freq_resp_cal_data = dict(fr) @property def noise_floor_cal_data(self) -> Dict[float, float]: return self._noise_floor_cal_data @noise_floor_cal_data.setter def noise_floor_cal_data(self, nf: Dict[float, float]) -> None: self._noise_floor_cal_data = dict(nf) def initialize_hw(self, ser: serial.Serial) -> None: """Initialize the ADC attenuator hardware. """ if self.stepatten_ctl: self.stepatten_ctl.initialize(ser) if self.lo1_synth_ctl: self.lo1_synth_ctl.initialize(ser) def initialize(self, ser: serial.Serial): self.configure_lo1(ser) def configure_stepatten(self, ser, att) -> None: if self.stepatten_ctl: self.stepatten_ctl.attenuation = att self.stepatten_ctl.configure(ser) def configure_lo1(self, ser) -> None: if self.lo1_synth_ctl: self.lo1_synth_ctl.configure(ser) def configure_lo1_freq(self, ser, freq) -> None: if self.lo1_synth_ctl: self.lo1_synth_ctl.freq = freq self.lo1_synth_ctl.configure_freq(ser)
App Class
It's possible to implement different types of front end depending on the hardware. Initially, the front end type will be frequency conversion (more properly, frequency down conversion). This may be done using direct conversion via a single LO and mixer stage or using a super heterodyne approach. Base band must be somewhere within the ADC sampling range which is approximately 0.5 to 55 MHz.
from typing import ( Optional, List, Dict ) from time import sleep from rfblocks import ( hmc833, DEFAULT_BAUDRATE, create_serial, write_cmd ) from tam import ( RP_ADC_MIN_FREQUENCY, RP_ADC_MAX_FREQUENCY, RP_ADC_MIN_SPAN, RP_ADC_MAX_REF_LEVEL, RP_ADC_MIN_REF_LEVEL, RP_ADC_REF_LEVEL_STEP, RP_ADC_MIN_ATT, RP_ADC_MAX_ATT, RP_ADC_ATT_STEP, Capability, ChannelCapabilities ) from PyQt5.QtCore import ( Qt, QObject, QCoreApplication, QThread, QTimer, pyqtSignal, pyqtSlot ) from adcchan import ADCChan class FrontEndApp(QObject): <<frontend-config>> NOISE_FLOOR: Dict = { 'channels': { ADCCH1_NAME: { 400000: (-91.238, 5.062), 200000: (-97.886, 5.586), 100000: (-100.887, 5.566), 50000: (-104.222, 5.531), 20000: (-106.397, 5.562), 10000: (-110.599, 5.494), 5000: (-114.466, 5.605), 2000: (-118.074, 5.581), 1000: (-120.604, 5.547), 500: (-123.662, 5.647), 200: (-127.920, 5.609), 100: (-130.131, 5.466), 50: (-134.579, 5.560), 20: (-137.322, 5.585), 10: (-140.600, 5.524), 5: (-144.054, 5.567), 2: (-148.833, 5.588), 1: (-149.271, 5.558) }, ADCCH2_NAME: { 400000: (-96.159, 5.025), 200000: (-100.022, 5.578), 100000: (-103.787, 5.623), 50000: (-104.291, 5.557), 20000: (-109.207, 5.540), 10000: (-111.787, 5.507), 5000: (-114.801, 5.553), 2000: (-117.145, 5.547), 1000: (-122.124, 5.590), 500: (-124.722, 5.503), 200: (-129.797, 5.479), 100: (-132.126, 5.532), 50: (-134.208, 5.563), 20: (-138.224, 5.507), 10: (-141.068, 5.564), 5: (-143.290, 5.563), 2: (-146.779, 5.515), 1: (-151.426, 5.648) } } } FREQ_RESPONSE: Dict = { 'channels': { ADCCH1_NAME: { 54.90: 2.083, 104.90: 1.571, 154.90: 1.569, 204.90: 1.587, 254.90: 1.473, 304.90: 1.568, 354.90: 1.499, 404.90: 1.558, 454.90: 1.667, 504.90: 1.604, 554.90: 1.596, 604.90: 1.629, 654.90: 1.647, 704.90: 1.718, 754.90: 1.767, 804.90: 1.729, 854.90: 1.575, 904.90: 1.653, 954.90: 1.614, 1004.90: 1.723, 1054.90: 1.863, 1104.90: 1.686, 1154.90: 1.743, 1204.90: 1.777, 1254.90: 1.724, 1304.90: 1.804, 1354.90: 1.905, 1404.90: 1.826, 1454.90: 1.865, 1504.90: 1.898, 1554.90: 2.013, 1604.90: 2.069, 1654.90: 1.991, 1704.90: 2.091, 1754.90: 2.122, 1804.91: 2.275, 1854.91: 2.407, 1904.91: 2.286, 1954.91: 2.005, 2004.91: 2.128, 2054.91: 2.316, 2104.91: 2.654, 2154.91: 2.628, 2204.91: 2.457, 2254.91: 2.424, 2304.91: 2.471, 2354.91: 2.619, 2404.91: 2.668, 2454.91: 2.936, 2504.91: 2.841, 2554.91: 2.912, 2604.91: 3.341, 2654.91: 3.317, 2704.91: 3.565, 2754.91: 3.584, 2804.91: 3.355, 2854.91: 3.127, 2904.91: 2.965, 2954.91: 2.903, 3004.91: 2.897, 3054.91: 3.119, 3104.91: 3.231, 3154.91: 3.020, 3204.91: 2.970, 3254.91: 3.002, 3304.91: 3.280, 3354.91: 3.434, 3404.91: 3.331, 3454.91: 3.277, 3504.91: 3.489, 3554.91: 4.341, 3604.91: 4.161, 3654.91: 4.148, 3704.91: 4.320, 3754.91: 5.010, 3804.91: 5.177, 3854.91: 4.701, 3904.91: 5.072, 3954.91: 5.529, 4004.91: 6.070, 4054.91: 6.081 }, ADCCH2_NAME: { 54.90: -0.156, 104.90: -0.336, 154.90: -0.118, 204.90: -0.074, 254.90: 0.019, 304.90: 0.091, 354.90: 0.087, 404.90: 0.017, 454.90: 0.163, 504.90: 0.161, 554.90: 0.092, 604.90: 0.211, 654.90: 0.280, 704.90: 0.495, 754.90: 0.625, 804.90: 0.568, 854.90: 0.640, 904.90: 0.779, 954.90: 0.804, 1004.90: 0.914, 1054.90: 1.008, 1104.90: 0.957, 1154.90: 0.810, 1204.90: 0.936, 1254.90: 0.897, 1304.91: 1.135, 1354.91: 1.161, 1404.91: 1.299, 1454.91: 1.441, 1504.91: 1.434, 1554.91: 1.468, 1604.91: 1.550, 1654.91: 1.413, 1704.91: 1.558, 1754.91: 1.613, 1804.91: 1.898, 1854.91: 1.951, 1904.91: 1.794, 1954.91: 1.750, 2004.91: 1.864, 2054.91: 2.024, 2104.91: 2.408, 2154.91: 2.413, 2204.91: 2.260, 2254.91: 2.397, 2304.91: 2.517, 2354.91: 2.783, 2404.91: 2.988, 2454.91: 3.396, 2504.91: 3.155, 2554.91: 3.539, 2604.91: 3.746, 2654.91: 4.130, 2704.91: 4.902, 2754.91: 4.936, 2804.91: 4.791, 2854.91: 4.636, 2904.91: 4.678, 2954.91: 4.406, 3004.91: 4.420, 3054.91: 5.162, 3104.91: 5.126, 3154.91: 5.248, 3204.91: 5.481, 3254.91: 5.828, 3304.91: 5.657, 3354.91: 5.623, 3404.91: 5.538, 3454.91: 5.348, 3504.91: 5.125, 3554.91: 5.667, 3604.91: 5.922, 3654.91: 5.920, 3704.91: 6.600, 3754.91: 7.614, 3804.91: 9.127, 3854.91: 8.324, 3904.91: 8.188, 3954.91: 8.687, 4004.92: 9.284, 4054.92: 9.173 } } } # Frequency response cal. with GRF2013 400-5000 MHz amp. on ADC CH1 and # GRF2013 50-3000 MHz amp on ADC CH2. # # FREQ_RESPONSE: Dict = { # 'channels': { # ADCCH1_NAME: { # 54.90: -18.281, 104.90: -18.987, 154.90: -18.954, 204.90: -18.935, # 254.90: -18.964, 304.90: -18.924, 354.90: -18.870, 404.90: -18.862, # 454.90: -18.484, 504.90: -18.497, 554.90: -18.552, 604.90: -18.332, # 654.90: -18.360, 704.90: -18.042, 754.90: -17.796, 804.90: -18.062, # 854.90: -18.034, 904.90: -17.858, 954.90: -17.973, 1004.90: -17.645, # 1054.90: -17.580, 1104.90: -17.628, 1154.90: -17.392, 1204.90: -16.994, # 1254.90: -16.764, 1304.90: -16.525, 1354.90: -16.388, 1404.90: -16.429, # 1454.90: -16.071, 1504.90: -15.557, 1554.90: -15.353, 1604.90: -15.390, # 1654.90: -15.375, 1704.90: -15.080, 1754.90: -14.864, 1804.90: -14.425, # 1854.90: -14.245, 1904.90: -14.511, 1954.90: -14.481, 2004.90: -14.325, # 2054.90: -14.033, 2104.90: -13.949, 2154.90: -14.041, 2204.90: -14.069, # 2254.90: -13.615, 2304.90: -13.448, 2354.90: -13.281, 2404.90: -13.194, # 2454.90: -13.830, 2504.90: -13.866, 2554.90: -13.612, 2604.90: -13.348, # 2654.90: -12.700, 2704.90: -12.100, 2754.90: -12.321, 2804.90: -12.155, # 2854.90: -12.258, 2904.90: -12.570, 2954.90: -12.436, 3004.90: -12.256, # 3054.90: -11.983, 3104.90: -11.599, 3154.90: -11.253, 3204.90: -11.096, # 3254.90: -11.096, 3304.90: -11.207, 3354.90: -11.236, 3404.90: -11.226, # 3454.90: -11.180, 3504.90: -10.545, 3554.90: -9.761, 3604.90: -9.401, # 3654.90: -8.822, 3704.90: -8.643, 3754.90: -8.645, 3804.90: -7.951, # 3854.90: -7.568, 3904.90: -6.816, 3954.90: -5.756, 4004.91: -5.527, # 4054.90: -4.872 # }, # ADCCH2_NAME: { # 54.90: -20.956, 104.90: -20.845, 154.90: -20.719, 204.90: -20.535, # 254.90: -20.417, 304.90: -20.259, 354.90: -20.233, 404.90: -20.295, # 454.90: -20.253, 504.90: -20.153, 554.90: -20.120, 604.90: -19.829, # 654.90: -19.696, 704.90: -19.299, 754.90: -19.259, 804.90: -19.172, # 854.90: -18.994, 904.90: -18.899, 954.90: -18.778, 1004.90: -18.518, # 1054.90: -18.496, 1104.90: -18.438, 1154.90: -18.326, 1204.90: -18.399, # 1254.91: -18.353, 1304.91: -18.113, 1354.91: -17.750, 1404.91: -17.507, # 1454.91: -17.075, 1504.91: -16.908, 1554.91: -16.971, 1604.91: -17.005, # 1654.91: -16.855, 1704.91: -16.439, 1754.91: -16.097, 1804.91: -15.762, # 1854.91: -15.809, 1904.91: -15.839, 1954.91: -15.539, 2004.91: -15.317, # 2054.91: -15.134, 2104.91: -14.870, 2154.91: -15.066, 2204.91: -14.902, # 2254.91: -14.567, 2304.91: -14.303, 2354.91: -13.757, 2404.91: -13.620, # 2454.91: -14.006, 2504.91: -14.047, 2554.91: -13.920, 2604.91: -13.163, # 2654.91: -12.214, 2704.91: -11.596, 2754.91: -11.410, 2804.91: -11.388, # 2854.91: -11.843, 2904.91: -12.211, 2954.91: -11.917, 3004.91: -11.041, # 3054.91: -9.934 # } # } # } def __init__(self, hw_config: List = APP_HWCONF, app_config: Dict = DEFAULT_APP_CONFIG) -> None: super().__init__() d: Dict = app_config['channels'] z = zip(d.keys(), hw_config, d.values()) self._channels: Dict = { chan_id: ADCChan(chan_id, hw_conf, chan_config) for chan_id, hw_conf, chan_config in z} for chan_id, nf in FrontEndApp.NOISE_FLOOR['channels'].items(): self._channels[chan_id].noise_floor_cal_data = nf for chan_id, fr in FrontEndApp.FREQ_RESPONSE['channels'].items(): self._channels[chan_id].freq_resp_cal_data = fr <<initialize>> @property def channels(self): return self._channels
Hardware configuration
{ "hw_config": [ {"adcatten": {"le": "D0"}, "stepatten": {"le": "D4"}, "lo1": {"sen": "D2", "ld_sdo": "D3"}}, {"adcatten": {"le": "D1"}, "stepatten": {"le": "C6"}, "lo1": {"sen": "D5", "ld_sdo": "D6"}} ], "app_config": { "channels": { "Ch 1": {"adcatten": {"atten": 0.0}, "stepatten": {"atten": 10.0}, "lo1": { "freq": 100.0, "ref_freq": 50.0, "refsrc": 0, "bufgain": 0, "divgain": 0, "vco_mute": false }}, "Ch 2": {"adcatten": {"atten": 0.0}, "stepatten": {"atten": 10.0}, "lo1": { "freq": 100.0, "ref_freq": 50.0, "refsrc": 0, "bufgain": 0, "divgain": 0, "vco_mute": false }} } } }
ADCCH1_NAME: str = 'Ch 1' ADCCH2_NAME: str = 'Ch 2' LO1_CH1_HWCONF: Dict = {'sen': 'D2', 'ld_sdo': 'D3'} LO2_CH1_HWCONF: Dict = {'sen': 'D5', 'ld_sdo': 'D6'} STEPATTEN_CH1_HWCONF: Dict = {'le': 'D4'} LO1_CH2_HWCONF: Dict = {'sen': 'C4', 'ld_sdo': 'C5'} LO2_CH2_HWCONF: Dict = {'sen': 'C7', 'ld_sdo': 'B7'} STEPATTEN_CH2_HWCONF: Dict = {'le': 'C6'} # The channel hardware is configured to match the hardware # currently in place. CH1_HWCONF: Dict = {'stepatten': STEPATTEN_CH1_HWCONF, 'lo1': LO1_CH1_HWCONF} CH2_HWCONF: Dict = {'stepatten': STEPATTEN_CH2_HWCONF, 'lo1': LO1_CH2_HWCONF} APP_HWCONF: List = [CH1_HWCONF, CH2_HWCONF] DEFAULT_ATTEN: float = 10.0 DEFAULT_LO1_FREQ: float = 100.0 DEFAULT_LO2_FREQ: float = 1000.0 DEFAULT_REF_FREQ: float = 50.0 DEFAULT_STEPATTEN_CONFIG: Dict = { 'atten': DEFAULT_ATTEN } DEFAULT_LO1_CONFIG: Dict = { 'freq': DEFAULT_LO1_FREQ, 'ref_freq': DEFAULT_REF_FREQ, 'refsrc': hmc833.DEFAULT_REFSRC, 'bufgain': hmc833.OutputBufferGain.MAXGAIN_MINUS_6DB, 'divgain': hmc833.DividerGain.MAXGAIN_MINUS_3DB, 'vco_mute': False } DEFAULT_LO2_CONFIG: Dict = { 'freq': DEFAULT_LO2_FREQ, 'ref_freq': DEFAULT_REF_FREQ, 'refsrc': hmc833.DEFAULT_REFSRC, 'bufgain': hmc833.OutputBufferGain.MAXGAIN_MINUS_6DB, 'divgain': hmc833.DividerGain.MAXGAIN_MINUS_3DB, 'vco_mute': False } DEFAULT_CHAN_CONFIG: Dict = { 'stepatten': DEFAULT_STEPATTEN_CONFIG, 'lo1': DEFAULT_LO1_CONFIG, 'lo2': DEFAULT_LO2_CONFIG } DEFAULT_APP_CONFIG: Dict = { 'channels': { ADCCH1_NAME: DEFAULT_CHAN_CONFIG, ADCCH2_NAME: DEFAULT_CHAN_CONFIG } }
The FrontEnd Service
MIN_ATT = 0.0 MAX_ATT = 31.75 ATT_STEP = 0.25 class FrontEndService(rpyc.Service): DEFAULT_CAPABILITIES = { "channels": [ FrontEndApp.ADCCH1_NAME, FrontEndApp.ADCCH2_NAME], FrontEndApp.ADCCH1_NAME: { "adcdma_channel": 0, "adc_attenuation": { "min": RP_ADC_MIN_ATT, "max": RP_ADC_MAX_ATT, "step": RP_ADC_ATT_STEP }, "fe_attenuation": { "min": MIN_ATT, "max": MAX_ATT, "step": ATT_STEP }, "freq": { "min": 55.0, "max": 4000.0, "step": 0.0 }, "cal_freq": { "min": 53.0, "max": 4055.0, "step": 50.0 }, "reflevel": { "min": RP_ADC_MIN_REF_LEVEL, "max": RP_ADC_MAX_REF_LEVEL, "step": 1.0 }, "min_span": RP_ADC_MIN_SPAN, "chan_type": ChannelCapabilities.SINGLE, "bandwidth": 45.0 }, FrontEndApp.ADCCH2_NAME: { "adcdma_channel": 1, "adc_attenuation": { "min": RP_ADC_MIN_ATT, "max": RP_ADC_MAX_ATT, "step": RP_ADC_ATT_STEP }, "fe_attenuation": { "min": MIN_ATT, "max": MAX_ATT, "step": ATT_STEP }, "freq": { "min": 55.0, "max": 4000.0, "step": 0.0 }, "cal_freq": { "min": 53.0, "max": 4055.0, "step": 50.0 }, "reflevel": { "min": RP_ADC_MIN_REF_LEVEL, "max": RP_ADC_MAX_REF_LEVEL, "step": 1.0 }, "min_span": RP_ADC_MIN_SPAN, "chan_type": ChannelCapabilities.SINGLE, "bandwidth": 45.0 } } def __init__(self, app, serial_device: str, baudrate: int = 0, capabilities: Dict = DEFAULT_CAPABILITIES) -> None: super().__init__() self._app = app self._ctl_device: str = serial_device self._baudrate: int = baudrate if baudrate == 0: self._baudrate = DEFAULT_BAUDRATE self._capabilities = { 'channels': capabilities['channels'], FrontEndApp.ADCCH1_NAME: ChannelCapabilities.from_dict( capabilities[FrontEndApp.ADCCH1_NAME]), FrontEndApp.ADCCH2_NAME: ChannelCapabilities.from_dict( capabilities[FrontEndApp.ADCCH2_NAME]) } self._ser = None self._client_count = 0 def on_connect(self, conn): if self._ser is None: self._ser = create_serial(self._ctl_device, self._baudrate) self._app.initialize_hw(self._ser) self._client_count += 1 def on_disconnect(self, conn): self._client_count -= 1 if self._client_count == 0: self._app.cleanup() self._ser.close() self._ser = None @property def capabilities(self): return self._capabilities @property def adc_channels(self): return self._app.channels def initialize(self): self._app.initialize(self._ser) def configure_stepatten(self, chan_id, att): ch = self.adc_channels[chan_id] ch.configure_stepatten(self._ser, att) def configure_lo1(self, chan_id): ch = self.adc_channels[chan_id] ch.configure_lo1(self._ser) def configure_lo1_freq(self, chan_id, lo_freq): ch = self.adc_channels[chan_id] ch.configure_lo1_freq(self._ser, lo_freq) @classmethod def save_config(cls): print(FrontEndApp.DEFAULT_APP_CONFIG) class RPyCServer(QObject): finished = pyqtSignal() def __init__(self, serviceInst, host, port): super().__init__() self._serviceInst = serviceInst self._host = host self._port = port def run(self): print("RPRF rpyc service on {}:{}".format(self._host, self._port)) self._server = ThreadedServer( self._serviceInst, hostname = self._host, port = self._port, auto_register = False, protocol_config = { 'allow_all_attrs': True, 'allow_setattr': True, 'allow_pickle': True}) self._server.start() self.finished.emit()
Single Channel Configuration
In some circumstances only one channel of the down converter will be in use. The other spectrum analyzer channel either remaining unused or being used to measure a signal in base band.
The spectrum analyzer test bed app can be configured to make use of the
single channel configuration by using the -F
command line parameter:
{ "hw_config": [ {"adcatten": {"le": "D0"}, "stepatten": {"le": "D4"}, "lo1": {"sen": "D2", "ld_sdo": "D3"}}, {"adcatten": {"le": "D1"}} ], "app_config": { "channels": { "Ch 1": {"adcatten": {"atten": 0.0}, "stepatten": {"atten": 16.0}, "lo1": { "freq": 100.0, "ref_freq": 50.0, "refsrc": 0, "bufgain": 0, "divgain": 0, "vco_mute": false }}, "Ch 2": {"adcatten": {"atten": 0.0}} } }, "capabilities": { "channels": ["Ch 1", "Ch 2"], "Ch 1": { "adcdma_channel": 0, "adc_attenuation": { "min": 0.0, "max": 31.75, "step": 0.25 }, "fe_attenuation": { "min": 0.0, "max": 31.75, "step": 0.25 }, "freq": { "min": 55.0, "max": 4000.0, "step": 0.0 }, "cal_freq": { "min": 53.0, "max": 4055.0, "step": 50.0 }, "reflevel": { "min": -100.0, "max": 20.0, "step": 1.0 }, "min_span": 0.0005, "chan_type": 1, "bandwidth": 45.0 }, "Ch 2": { "adcdma_channel": 1, "adc_attenuation": { "min": 0.0, "max": 31.75, "step": 0.25 }, "fe_attenuation": null, "freq": { "min": 0.5, "max": 55.0, "step": 0.0 }, "cal_freq": { "min": 0.1, "max": 56.5, "step": 0.5 }, "reflevel": { "min": -100.0, "max": 20.0, "step": 1.0 }, "min_span": 0.0005, "chan_type": 0, "bandwidth": 45.0 } } }
Imports
from typing import ( Optional, List, Dict ) import sys from time import sleep import signal import json from argparse import ArgumentParser import asyncio import serial from rfblocks import ( hmc833, DEFAULT_BAUDRATE, create_serial, write_cmd ) from tam import ( RP_ADC_MIN_FREQUENCY, RP_ADC_MAX_FREQUENCY, RP_ADC_MIN_SPAN, RP_ADC_MAX_REF_LEVEL, RP_ADC_MIN_REF_LEVEL, RP_ADC_REF_LEVEL_STEP, RP_ADC_MIN_ATT, RP_ADC_MAX_ATT, RP_ADC_ATT_STEP, Capability, ChannelCapabilities ) import rpyc from rpyc.utils.server import ThreadedServer from qasync import ( QEventLoop, QThreadExecutor, asyncSlot, asyncClose ) from PyQt5.QtCore import ( Qt, QObject, QCoreApplication, QThread, QTimer, pyqtSignal, pyqtSlot ) from adcchan import ADCChan from frontend import FrontEndApp