import numpy as np
from dataclasses import dataclass
from logging import getLogger
from denspp.offline.analog.common_func import CommonAnalogFunctions
[docs]
@dataclass
class SettingsComparator:
"""Individual data class to configure an analogue voltage comparator
Attributes:
vdd: Positive supply voltage [V],
vss: Negative supply voltage [V],
gain: Amplification factor of comparator [V/V]
offset: Offset voltage of the amplifier [V] without VCM,
noise_dis: Voltage Noise distribution of comparator [V/sqrt(Hz)]
hysteresis: Hysteresis voltage window of comparator [%]
out_analog: Is output analog [True] or digital [False]
out_invert: Is output active low [True] or active high [False]
"""
vdd: float
vss: float
# Comparator characteristics
gain: int
offset: float
noise_dis: float
hysteresis: float
out_analog: bool
out_invert: bool
DefaultSettingsComparator = SettingsComparator(
vdd=0.6, vss=-0.6,
gain=100,
offset=-1e-3,
noise_dis=0.1e-3,
hysteresis=0.25,
out_analog=False,
out_invert=False,
)
[docs]
class Comparator(CommonAnalogFunctions):
_settings: SettingsComparator
_unoise: np.ndarray
_int_state: bool
@property
def get_noise_signal(self) -> np.ndarray:
return self._unoise
def __init__(self, settings_dev: SettingsComparator) -> None:
"""Class for emulating an analogue comparator
:param settings_dev: Dataclass for handling the comparator amplifier
"""
super().__init__()
self.__logger = getLogger(__name__)
self.define_voltage_range(volt_low=settings_dev.vss, volt_hgh=settings_dev.vdd)
self._settings = settings_dev
def __generate_noise(self, input_size: int, scale: float=0.1, use_noise: bool=True) -> np.ndarray:
return np.random.normal(self._settings.offset, scale, input_size) if use_noise else self._settings.offset * np.ones(input_size)
def __apply_gain_comparator(self, uinp: np.ndarray | float, uinn: np.ndarray | float) -> np.ndarray:
du = np.array(uinp - uinn)
return self.vcm + self._settings.gain * (du - self._settings.offset)
def __generate_output(self, ucmp: np.ndarray) -> np.ndarray:
return np.array(ucmp >= self.vcm + self._settings.offset) if not self._settings.out_analog else ucmp
def __apply_inverter(self, u_cmp: np.ndarray | float) -> np.ndarray:
return (np.invert(u_cmp) if not self._settings.out_analog else 2* self.vcm - u_cmp) if self._settings.out_invert else u_cmp
def __apply_hysteresis(self, du: np.ndarray, mode: int) -> np.ndarray:
"""Processing differential input for generating hysteresis"""
thr = self.__type_hysteresis(mode)
u_out = np.zeros(du.shape)
u_out += self._unoise
self._int_state = False
# int_state == 0 --> not active, int_state == 1 --> active
for idx, val in enumerate(du):
thr_run = thr[0] if not self._int_state else thr[1]
out = np.subtract(val, thr_run)
self._int_state = np.sign(out) == 1
u_out[idx] = self.vcm + self._settings.gain * out
return u_out
def __type_hysteresis(self, mode: int) -> list:
"""Definition of type"""
thr_zero = self._settings.offset
thr_pos = thr_zero + self._settings.hysteresis * (self._settings.vdd - self.vcm)
thr_neg = thr_zero + self._settings.hysteresis * (self._settings.vss - self.vcm)
self.__logger.debug(f"Pos. hysterese window voltage at: {thr_pos} V")
self.__logger.debug(f"Neg. hysterese window voltage at: {thr_neg} V")
match mode:
case 1:
# --- Single Side, negative VSS
list_out = [thr_zero, thr_neg]
case 2:
# --- Single Side, positive VDD
list_out = [thr_pos, thr_zero]
case 3:
# --- Double Side, VSS-VDD
list_out = [thr_pos, thr_neg]
case _:
# --- Normal comparator
list_out = [thr_zero, thr_zero]
return list_out
[docs]
def cmp_ideal(self, uinp: np.ndarray | float, uinn: np.ndarray | float) -> np.ndarray:
"""Performs an ideal comparator with input signal (with offset, but no noise)
Args:
uinp - Positive input voltage [V]
uinn - Negative input voltage [V]
Returns:
Corresponding numpy array with boolean or voltage values (depends on out_analog)
"""
u_cmp = self.__apply_gain_comparator(uinp, uinn)
self._unoise = self.__generate_noise(input_size=u_cmp.size, scale=self._settings.noise_dis, use_noise=False)
u_cmp = self.clamp_voltage(u_cmp + self._unoise)
return self.__apply_inverter(self.__generate_output(u_cmp))
[docs]
def cmp_normal(self, uinp: np.ndarray | float, uinn: np.ndarray | float) -> np.ndarray:
"""Performs a normal comparator with input signal (with noise and offset)
Args:
uinp - Positive input voltage [V]
uinn - Negative input voltage [V]
Returns:
Corresponding numpy array with boolean or voltage values (depends on out_analog)
"""
u_cmp = self.__apply_gain_comparator(uinp, uinn)
self._unoise = self.__generate_noise(input_size=u_cmp.size, scale=self._settings.noise_dis, use_noise=True)
u_out = self.clamp_voltage(u_cmp + self._unoise)
return self.__apply_inverter(self.__generate_output(u_out))
[docs]
def cmp_single_pos_hysteresis(self, uinp: np.ndarray | float, uinn: np.ndarray | float) -> np.ndarray:
"""Performs a single-side hysteresis comparator with input signal (with noise and offset)
Args:
uinp: Positive input voltage [V]
uinn: Negative input voltage [V]
Returns:
Corresponding numpy array with boolean values
"""
du = np.array(uinp - uinn)
self._unoise = self.__generate_noise(input_size=du.size, scale=self._settings.noise_dis, use_noise=True)
u_out = self.clamp_voltage(self.__apply_hysteresis(du, 2))
return self.__apply_inverter(self.__generate_output(u_out))
[docs]
def cmp_single_neg_hysteresis(self, uinp: np.ndarray | float, uinn: np.ndarray | float) -> np.ndarray:
"""Performs a single-side hysteresis comparator with input signal (with noise and offset)
Args:
uinp: Positive input voltage [V]
uinn: Negative input voltage [V]
Returns:
Corresponding numpy array with boolean values
"""
du = np.array(uinp - uinn)
self._unoise = self.__generate_noise(input_size=du.size, scale=self._settings.noise_dis, use_noise=True)
u_out = self.clamp_voltage(self.__apply_hysteresis(du, 1))
return self.__apply_inverter(self.__generate_output(u_out))
[docs]
def cmp_double_hysteresis(self, uinp: np.ndarray | float, uinn: np.ndarray | float) -> np.ndarray:
"""Performs a double-side hysteresis comparator with input signal (with noise and offset)
Args:
uinp: Positive input voltage [V]
uinn: Negative input voltage [V]
Returns:
Corresponding numpy array with boolean values
"""
du = np.array(uinp - uinn)
self._unoise = self.__generate_noise(input_size=du.size, scale=self._settings.noise_dis, use_noise=True)
u_out = self.clamp_voltage(self.__apply_hysteresis(du, 3))
return self.__apply_inverter(self.__generate_output(u_out))