Source code for denspp.offline.analog.amplifier.pre_amp
import numpy as np
from dataclasses import dataclass
from scipy.signal import butter, lfilter, square
from denspp.offline.analog.common_func import CommonAnalogFunctions
from denspp.offline.analog.dev_noise import ProcessNoise, SettingsNoise, RecommendedSettingsNoise
[docs]
@dataclass
class SettingsAMP:
"""Individual data class to configure the PreAmp
Attributes:
vdd: Positive supply voltage [V]
vss: Negative supply voltage [V]
fs_ana: Sampling frequency of input [Hz]
gain: Amplification [V/V]
n_filt: Order of filter stage []
f_filt: Frequency range of filtering [Hz]
offset: Offset voltage of the amplifier [V]
f_chop: Chopping frequency [Hz] (for chopper)
noise_en: Enable noise on output [True/False]
noise_edev: Input voltage noise spectral density [V/sqrt(Hz)]
"""
vdd: float
vss: float
fs_ana: float
# Amplifier characteristics
gain: int
n_filt: int
f_filt: list
f_type: str
offset: float
# Chopper properties
f_chop: float
# Noise properties
noise_en: bool
noise_edev: float
@property
def vcm(self) -> float:
return (self.vdd + self.vss) / 2
RecommendedSettingsAMP = SettingsAMP(
vdd=0.6, vss=-0.6,
fs_ana=50e3, gain=40,
n_filt=1, f_filt=[0.1, 8e3], f_type="bandpass",
offset=0e-6,
f_chop=10e3,
noise_en=False,
noise_edev=100e-9
)
[docs]
class PreAmp(CommonAnalogFunctions):
_handler_noise: ProcessNoise
_settings: SettingsAMP
__print_device = "pre-amplifier"
def __init__(self, settings_dev: SettingsAMP, settings_noise: SettingsNoise=RecommendedSettingsNoise):
"""Class for emulating an analogue pre-amplifier
:param settings_dev: Dataclass for handling the pre-amplifier
:param settings_noise: Dataclass for handling the noise simulation
"""
super().__init__()
self.define_voltage_range(volt_low=settings_dev.vss, volt_hgh=settings_dev.vdd)
self._handler_noise = ProcessNoise(settings_noise, settings_dev.fs_ana)
self._settings = settings_dev
# --- Filter properties
self.__coeffb, self.__coeffa = butter(
N=self._settings.n_filt,
Wn=2 * np.array(self._settings.f_filt) / self._settings.fs_ana,
btype=self._settings.f_type,
analog=False
)
@property
def get_filter_coeffs(self) -> dict:
"""Getting the filter coefficients"""
return {'b': self.__coeffb, 'a': self.__coeffa}
def __gen_chop(self, size: int) -> np.ndarray:
"""Generate the chopping clock signal"""
t = np.arange(0, size, 1) / self._settings.fs_ana
clk_chop = square(2 * np.pi * t * self._settings.f_chop, duty=0.5)
return clk_chop
def __noise_generation_circuit(self, size: int) -> np.ndarray:
"""Generating of noise using circuit noise properties"""
if self._settings.noise_en:
u_out = self._handler_noise.gen_noise_awgn_dev(size, self._settings.noise_edev)
else:
u_out = np.zeros((size,))
return u_out
[docs]
def pre_amp(self, uinp: np.ndarray, uinn: np.ndarray | float) -> np.ndarray:
"""Performs the pre-amplification (single, normal) with input signal
Args:
uinp: Positive input voltage [V]
uinn: Negative input voltage [V]
Returns:
Corresponding numpy array with output voltage signal
"""
du = uinp - uinn
u_out = self._settings.gain * lfilter(b=self.__coeffb, a=self.__coeffa, x=du)
u_out += self._settings.gain * self._settings.offset
u_out += self._settings.vcm
u_out += self._settings.gain * self.__noise_generation_circuit(du.size)
return self.clamp_voltage(u_out)
[docs]
def pre_amp_chopper(self, uinp: np.ndarray, uinn: np.ndarray | float) -> dict:
"""Performs the pre-amplification (single, chopper) with input signal
Args:
uinp: Positive input voltage
uinn: Negative input voltage
Returns:
Dictionary with results ['out' = Output voltage from pre-amp, 'chop' = chopped voltage signal]
"""
du = uinp - uinn
clk_chop = self.__gen_chop(du.size)
# --- Chopping
du = (du + self._settings.offset - self._settings.vcm) * clk_chop
uchp_in = self._settings.vcm + self._settings.gain * du
uchp_in += self._settings.gain * self.__noise_generation_circuit(du.size)
# --- Back chopping and Filtering
u_filt = uchp_in * clk_chop
u_out = lfilter(self.__coeffb, self.__coeffa, u_filt)
u_out += self._settings.vcm
return {'out': self.clamp_voltage(u_out), 'chop': self.clamp_voltage(uchp_in)}