From b9aab4c9ab542823ec751e32ea7462428d98419c Mon Sep 17 00:00:00 2001 From: andreas128 Date: Mon, 4 Sep 2017 20:05:28 +0200 Subject: Add TX_Agc class for TX automatic gain control --- dpd/main.py | 15 ++++++++ dpd/src/TX_Agc.py | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 dpd/src/TX_Agc.py (limited to 'dpd') diff --git a/dpd/main.py b/dpd/main.py index 57bfed6..5e4f29f 100755 --- a/dpd/main.py +++ b/dpd/main.py @@ -12,6 +12,7 @@ predistortion module of ODR-DabMod.""" import datetime import os +import time import logging @@ -34,11 +35,13 @@ console.setFormatter(formatter) # add the handler to the root logger logging.getLogger('').addHandler(console) +import numpy as np import traceback import src.Measure as Measure import src.Model as Model import src.Adapt as Adapt import src.Agc as Agc +import src.TX_Agc as TX_Agc import src.Symbol_align import src.const import src.MER @@ -66,6 +69,10 @@ parser.add_argument('--rxgain', default=30, help='TX Gain', required=False, type=int) +parser.add_argument('--digital_gain', default=1.0, + help='Digital Gain', + required=False, + type=float) parser.add_argument('--samps', default='81920', help='Number of samples to request from ODR-DabMod', required=False) @@ -81,6 +88,7 @@ cli_args = parser.parse_args() port = int(cli_args.port) port_rc = int(cli_args.rc_port) coef_path = cli_args.coefs +digital_gain = cli_args.digital_gain txgain = cli_args.txgain rxgain = cli_args.rxgain num_req = int(cli_args.samps) @@ -99,6 +107,7 @@ if cli_args.load_poly: model = Model.Model(coefs_am, coefs_pm, plot=True) else: model = Model.Model([1, 0, 0, 0, 0], [0, 0, 0, 0, 0], plot=True) +adapt.set_txgain(digital_gain) adapt.set_txgain(txgain) adapt.set_rxgain(rxgain) adapt.set_coefs(model.coefs_am, model.coefs_pm) @@ -112,6 +121,8 @@ logging.info( ) ) +tx_agc = TX_Agc.TX_Agc(adapt) + # Automatic Gain Control agc = Agc.Agc(meas, adapt) agc.run() @@ -120,6 +131,10 @@ for i in range(num_iter): try: txframe_aligned, tx_ts, rxframe_aligned, rx_ts, rx_median = meas.get_samples() logging.debug("tx_ts {}, rx_ts {}".format(tx_ts, rx_ts)) + assert tx_ts - rx_ts < 1e-5, "Time Stamps do not match." + if tx_agc.adapt_if_necessary(txframe_aligned): + continue + coefs_am, coefs_pm = model.get_next_coefs(txframe_aligned, rxframe_aligned) adapt.set_coefs(coefs_am, coefs_pm) diff --git a/dpd/src/TX_Agc.py b/dpd/src/TX_Agc.py new file mode 100644 index 0000000..832c4d4 --- /dev/null +++ b/dpd/src/TX_Agc.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +# +# Automatic Gain Control +# +# http://www.opendigitalradio.org +# Licence: The MIT License, see notice at the end of this file + +import datetime +import os +import logging +import time + +logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) + +import numpy as np +import matplotlib + +matplotlib.use('agg') +import matplotlib.pyplot as plt + +import src.Adapt as Adapt +import src.Measure as Measure + + +#TODO fix for float tx_gain +class TX_Agc: + def __init__(self, + adapt, + max_txgain=70, + tx_max_target=0.85, + tx_max_threshold_max=0.95, + tx_max_threshold_min=0.65): + """ + In order to avoid digital clipping, this class increases the + TX gain and reduces the digital gain. Digital clipping happens + when the digital analog converter receives values greater than + it's maximal output. This class solves that problem by adapting + the TX gain in a way that the peaks of the TX signal are in a + specified range. The TX gain is adapted accordingly. + + :param adapt: Instance of Adapt Class to update + txgain and coefficients + :param max_txgain: limit for TX gain + :param tx_max_threshold_max: if the maximum of TX is larger + than this value, then the digital gain is reduced + :param tx_max_threshold_min: if the maximum of TX is smaller + than this value, then the digital gain is increased + :param tx_max_target: The digital gain is reduced in a way that + the maximal TX value is expected to be lower than this value. + """ + assert isinstance(adapt, Adapt.Adapt) + self.adapt = adapt + self.max_txgain = max_txgain + self.txgain = self.max_txgain + + assert tx_max_threshold_max > tx_max_target,\ + "The tolerated tx_max has to be larger then the goal tx_max" + self.tx_max_threshold_tolerate_max = tx_max_threshold_max + self.tx_max_threshold_tolerate_min = tx_max_threshold_min + self.tx_max_target = tx_max_target + + def adapt_if_necessary(self, tx): + tx_max = np.max(np.abs(tx)) + if tx_max > self.tx_max_threshold_tolerate_max or\ + tx_max < self.tx_max_threshold_tolerate_min: + delta_db = \ + np.floor(20 * np.log10(self.tx_max_target / tx_max)).astype(int) + new_txgain = self.adapt.get_txgain() - delta_db + assert new_txgain < self.max_txgain,\ + "TX_Agc failed. New TX gain of {} is too large.".format( + new_txgain + ) + self.adapt.set_txgain(new_txgain) + txgain = self.adapt.get_txgain() + + digital_gain_factor = 10 ** (delta_db / 20.) + digital_gain = self.adapt.get_digital_gain() * digital_gain_factor + self.adapt.set_digital_gain(digital_gain) + + logging.info( + "digital_gain = {}, txgain_new = {}, " \ + "delta_db = {}, tx_max {}, " \ + "digital_gain_factor = {}". + format(digital_gain, txgain, delta_db, + tx_max, digital_gain_factor)) + + time.sleep(1) + return True + return False + +# The MIT License (MIT) +# +# Copyright (c) 2017 Andreas Steger +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. -- cgit v1.2.3