# `pe43711` - Encapsulates control of the PE43711 step attenuator.
#
# Copyright (C) 2020 Dyadic Pty Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import (
Optional, Callable
)
[docs]class AttenuationRangeException(Exception):
"""Raised when a requested attenuation is out of range.
"""
pass
[docs]class pe43711(object):
"""Encapsulates control of the PE43711 step attenuator.
Documentation for the PE43711 step attenuator rfblocks module
can be found here: `A PE43711 Step Attenuator <../boards/PE43711-Step-Attenuator.html>`_
"""
MIN_ATTENUATION = 0.0
MAX_ATTENUATION = 31.75
STEP_SIZE = 0.25
[docs] @classmethod
def default_insertion_loss(cls, freq: float) -> float:
"""Calculates attenuator default insertion loss.
By default the attenuator insertion loss is modelled as a
linear function. The slope and intercept of the function are
derived from VNA measurements of the attenuator S21 with an
attenuation setting of 0 dB.
:param freq: Frequency (in MHz) for computing the insertion loss.
:type freq: float
:return: The attenuator insertion loss (in dB) for an attenuation
setting of 0 dB at the specified frequency.
"""
DEFAULT_INSLOSS_INTERCEPT = -1.17
DEFAULT_INSLOSS_SLOPE = -3.29e-4
return DEFAULT_INSLOSS_INTERCEPT + (DEFAULT_INSLOSS_SLOPE * freq)
def __init__(self, le: str = None) -> None:
"""
:param le: The PE43711 latch enable (`LE`) controller pin
:type le: str
"""
self.le = le.upper() if le else le
self._insertion_loss_fn = pe43711.default_insertion_loss
def __str__(self) -> str:
"""
"""
return 'PE43711: le: {}'.format(self.le)
def __repr__(self):
return self.__str__()
[docs] def pin_config(self) -> str:
"""Initialize controller pin configuration.
:return: A string specifying the commands required to initialize the
connected controller pins.
"""
cmd = ''
if self.le:
cmd += 'O{}:L{}:'.format(self.le, self.le)
return cmd
[docs] def chip_reset(self) -> str:
"""Reset the chip internal logic to default states.
:return: A string containing the controller commands required to reset
the chip.
"""
return ""
[docs] def atten_word(self, attn_db: float) -> str:
"""Calculate the attenuation word for a specified attenuation level.
:param attn_db: The desired attenuation level in dB.
This should be in the range 0 to 31.75 dB.
:type attn_db: float
:return: An 8-bit integer containing the attenuation 'word'.
:raises: AttenuationRangeException
If the specified attenuation is out of range.
"""
if attn_db < 0 or attn_db > 31.75:
raise AttenuationRangeException
else:
n = int(round(attn_db * 4))
# Reverse the bit order. PE43711 wants the atten. setting
# least significant bit first.
return int('{:08b}'.format(n)[::-1], 2)
[docs] def config_atten_word(self, attn_word: int) -> str:
"""Update the current attenuation word.
:param attn_word: The attenuator word for the desired
attenuation. Note that only the lower
7 bits of the value are used.
:type attn_word: 8-bit integer
:return: The command string required to update the current
device attentuation value.
"""
cmd = ''
if attn_word is not None:
attn_word = attn_word
astr = '{:02X}'.format(attn_word)
cmd = 'W{}:H{}:L{}:'.format(astr[0:2], self.le, self.le)
return cmd
[docs] def insertion_loss(self, freq: float) -> float:
"""Calculate the attenuator insertion loss. Insertion loss is
computed using a 0 dB attenator setting.
:param freq: Frequency, in MHz, at which to compute the insertion
loss.
:type freq: float
:return: The insertion loss (in dB) at the specified frequency.
"""
return self._insertion_loss_fn(freq)
[docs] def set_insertion_loss_fn(self,
fn: Optional[Callable[[float], float]]) -> None:
"""Set a callable to use when calculating the attenuator insertion
loss.
:param fn: A callable which takes a single float parameter specifying
the frequency in MHz and returning the insertion loss in dB.
"""
self._insertion_loss_fn = fn
if __name__ == '__main__':
att1 = pe43711('d1')
print(' att1: {}'.format(att1))
print("att1 pin conf: {}".format(att1.pin_config()))
print("test set attenuation:")
print(" 10dB: {}".format(att1.config_atten_word(att1.atten_word(10.0))))
print(" 20dB: {}".format(att1.config_atten_word(att1.atten_word(20.0))))
print(" 30dB: {}".format(att1.config_atten_word(att1.atten_word(30.0))))
print(" 0dB: {}".format(att1.config_atten_word(att1.atten_word(0.0))))