#!   /usr/bin/env   python
#    coding: utf8

# Copyright CERN, 2011
# Author: Matthieu Cattin <matthieu.cattin@cern.ch>
# Licence: GPL v2 or later.
# Website: http://www.ohwr.org
# Last modifications: 7/6/2012

# Import system modules
import sys
import time
import os

# Add common modules and libraries location to path
sys.path.append('../../../')
sys.path.append('../../../gnurabbit/python/')
sys.path.append('../../../common/')
sys.path.append('../../fmceeprom/python/')

# Import common modules
from ptsexcept import *
from fmc_eeprom import *
import rr

# Import specific modules
from fmc_adc_spec import *
from fmc_adc import *
from numpy import *
from pylab import *
from calibr_box import *
from cp210x_eeprom import *
import find_usb_tty
from PAGE.Agilent33250A import *
from PAGE.SineWaveform import *
from ctypes import *



"""
test25: Calibration verification (max error in % FS)

Note: Requires test00.py to run first to load the firmware!
      Requires test19.py to run before to get calibration data.
      Requires test23.py to run before to write the calibration data to the FMC EEPROM
"""

NB_CHANNELS = 4

AWG_SET_SLEEP = 0.3
SSR_SET_SLEEP = 0.005
BOX_SET_SLEEP = 0.01
DAC_SET_SLEEP = 0.01

ACQ_TIMEOUT = 10

MAX_FIRMWARE_RELOAD = 10

PRE_TRIG_SAMPLES = 100
POST_TRIG_SAMPLES = 100000
NB_SHOTS = 1

ACQ_LENGTH = 50000 # in samples

ADC_NBITS = 16 # ADC chip is 14 bits, but shifted to 16 bits in the firmware
DAC_NBITS = 16
DAC_FS = 10 # DAC full scale range is 10V

# Full scale volatge values for input ranges
ADC_FS = {'10V':10.0, '1V':1.0, '100mV':0.1}

RANGES = ['10V', '1V', '100mV']

# Number of repeatition of the measurement, then the results are averaged
REPEAT = 2

ADC_LSB = {'10V':10.0/2**14, '1V':1.0/2**14, '100mV':0.1/2**14}
DAC_LSB = 10.0/2**16

# Maximum reference voltage ripple in LSB
MAX_RIPPLE_LSB = 2

# Maximum offset error in % FS
MAX_OFFSET_ERROR = 0.1

# Maximum gain error in % FS
MAX_GAIN_ERROR = 0.1


# Disconnect inputs of all channels
def disconnect_channels(fmc):
    for i in range(1,NB_CHANNELS+1):
        fmc.set_ssr(i, 0x00)
        time.sleep(SSR_SET_SLEEP)

# Initialise acquisition
def fmc_adc_init(spec, fmc):
    print "Initialise FMC board.\n"
    # Reset offset DACs
    fmc.dc_offset_reset()
    # Make sure all switches are OFF
    disconnect_channels(fmc)
    # Reset offset DACs
    fmc.dc_offset_reset()
    # Set trigger
    fmc.set_soft_trig()
    # Set acquisition
    fmc.set_pre_trig_samples(PRE_TRIG_SAMPLES)
    fmc.set_post_trig_samples(POST_TRIG_SAMPLES)
    fmc.set_shots(NB_SHOTS)
    # Print configuration
    #fmc.print_adc_core_config()

# Converts two's complement hex to signed
def hex2signed(value):
    if(value & 0x8000):
        return -((~value & 0xFFFF) + 1)
    else:
        return value

# Converts signed to two's complement hex
def signed2hex(value):
    if value < 0:
        return (((abs(value) ^ 0xffff) + 1) & 0xffff)
    else:
        return value

# Converts digital value to volts with half full range offset
def digital2volt(value, full_scale, nb_bit):
    return (float(value) * float(full_scale)/2**nb_bit) - full_scale/2.0

# Converts digital value to volts
def digital2volt_without_offset(value, full_scale, nb_bit):
    return float(value) * float(full_scale)/2**nb_bit

# Converts volts to digital value with half full range offset
def volt2digital(value, full_scale, nb_bit):
    digital = (value + full_scale/2) * 2**nb_bit/full_scale
    if(digital > 2**nb_bit - 1):
        digital = 2**nb_bit - 1
    if(digital < 0):
        digital = 0
    #print "volt2digital: %2.9f > %2.9f"%(value,digital)
    return int(digital)

# Converts volts to digital value
def volt2digital_without_offset(value, full_scale, nb_bit):
    if(value > (2**nb_bit)/2 - 1):
        value = (2**nb_bit)/2 - 1
    if(value < -((2**nb_bit)/2)):
        value = -((2**nb_bit)/2)
    digital = (value) * 2**nb_bit/full_scale
    #print "volt2digital: %2.9f > %2.9f"%(value,digital)
    return int(digital)

# Make an acquisition of a channel
def acq_channel(carrier, fmc, ch, adc_fs, adc_nbits=16, pause=0.01):
    # Make sure no acquisition is running
    fmc.stop_acq()
    # Start acquisition
    fmc.start_acq()
    #time.sleep(pause)
    # Trigger
    fmc.sw_trig()
    # Wait end of acquisition
    timeout = 0
    time.sleep(0.001)
    while('IDLE' != fmc.get_acq_fsm_state()):
        time.sleep(.01)
        timeout += 1
        if(ACQ_TIMEOUT < timeout):
            print "Acquisition timeout. Missing trigger?."
            print "Acq FSm state: %s"%fmc.get_acq_fsm_state()
            return 1
    # Retrieve data trough DMA
    trig_pos = fmc.get_trig_pos()
    # Enable "DMA done" interrupt
    carrier.enable_dma_done_irq()
    # Read ACQ_LENGTH samples after the trigger for all channels
    channels_data = carrier.get_data((trig_pos<<3), ACQ_LENGTH*8)
    # Disable "DMA done" interrupt
    carrier.disable_dma_done_irq()
    channels_data = [hex2signed(item) for item in channels_data]
    channels_data = [digital2volt_without_offset(item,adc_fs,adc_nbits) for item in channels_data]
    channel_data = channels_data[ch-1::4]
    return mean(channel_data)

# Set channel offset with the DAC
def set_offset_dac(fmc, channel, in_range, offset_volt, dac_fs=10.0, dac_nbits=16, dac_corr_flag=False):
    dac_v = offset_volt
    dac_d = volt2digital(dac_v,dac_fs,dac_nbits)
    dac_r = digital2volt(dac_d,dac_fs,dac_nbits)
    #print "DAC requested: %2.9f hex:%.4X actual:%2.9f"%(dac_v, dac_d, dac_r)
    if(True == dac_corr_flag):
        fmc.set_dc_offset_corrected(channel, in_range, dac_d, print_value=False)
    else:
        fmc.set_dc_offset(channel,dac_d)
    time.sleep(DAC_SET_SLEEP)

# Make a measurement, where the input and offset DAC values are set to a known value
def make_meas(carrier, fmc, box, dac_value, in_value, in_range, adc_fs, repeat):
    ch_meas = []
    ch_diff = []
    for ch in range(1,NB_CHANNELS+1):
        # Set offset DAC
        set_offset_dac(fmc, ch, in_range, dac_value, dac_corr_flag=True)
        # Configure calibration box
        if in_value == 0.0:
            # Configure analogue input, ground input
            fmc.set_input_range(ch, ('CAL_'+in_range))
            time.sleep(SSR_SET_SLEEP)
            # Connect AWG to current channel
            box.select_output_ch(ch)
            time.sleep(BOX_SET_SLEEP)
        else:
            # Configure analogue input range
            fmc.set_input_range(ch, in_range)
            time.sleep(SSR_SET_SLEEP)
            # Connect Vref for selected input range to current channel
            # Note that channel in parameter is the one connected to AWG
            if ch == 1:
                awg_ch = 4
            else:
                awg_ch = ch-1
            box.select_output_ch(awg_ch, in_range)
            time.sleep(BOX_SET_SLEEP)
        # Perform an acquisition
        meas = []
        for i in range(repeat):
            meas.append(acq_channel(carrier, fmc, ch, adc_fs))
        # Calculate mean and delta of acquisitions
        ch_meas.append(mean(meas))
        ch_diff.append(max(meas)-min(meas))
        meas_diff = max(meas)-min(meas)
    return ch_meas, ch_diff


def main (default_directory = '.'):

    # Constants declaration
    TEST_NB = 25
    EXPECTED_BITSTREAM_TYPE = 0x1

    # Calibration box vendor and product IDs
    BOX_USB_VENDOR_ID = 0x10c4 # Cygnal Integrated Products, Inc.
    BOX_USB_PRODUCT_ID = 0xea60 # CP210x Composite Device
    # Agilent AWG serial access vendor and product IDs
    AWG_USB_VENDOR_ID = 0x0403 # Future Technology Devices International, Ltd
    AWG_USB_PRODUCT_ID = 0x6001 # FT232 USB-Serial (UART) IC
    AWG_BAUD = 57600

    CALIBR_FILENAME = "calibration_data.txt"
    CALIBR_FILENAME = os.path.join(default_directory, CALIBR_FILENAME)
    CALIBR_BIN_FILENAME = "calibration_data.bin"
    CALIBR_BIN_FILENAME = os.path.join(default_directory, CALIBR_BIN_FILENAME)
    EEPROM_BIN_FILENAME = "eeprom_content.out"
    EEPROM_BIN_FILENAME = os.path.join(default_directory, EEPROM_BIN_FILENAME)
    EEPROM_SIZE = 8192 # in Bytes


    start_test_time = time.time()
    print "================================================================================"
    print "Test%02d start\n" % TEST_NB

    # SPEC object declaration
    print "Loading hardware access library and opening device.\n"
    spec = rr.Gennum()

    # Carrier object declaration (SPEC board specific part)
    # Used to check that the firmware is loaded.
    try:
        carrier = CFmcAdc100mSpec(spec, EXPECTED_BITSTREAM_TYPE)
    except FmcAdc100mSpecOperationError as e:
        raise PtsCritical("Carrier init failed, test stopped: %s" % e)

    # Mezzanine object declaration (FmcAdc100m14b4cha board specific part)
    try:
        fmc = CFmcAdc100m(spec)
    except FmcAdc100mOperationError as e:
        raise PtsCritical("Mezzanine init failed, test stopped: %s" % e)


    try:
        # Others objects declaration
        usb_tty = find_usb_tty.CttyUSB()
        awg_tty = usb_tty.find_usb_tty(AWG_USB_VENDOR_ID, AWG_USB_PRODUCT_ID)
        box_tty = usb_tty.find_usb_tty(BOX_USB_VENDOR_ID, BOX_USB_PRODUCT_ID)
        gen = Agilent33250A(device=awg_tty[0], bauds=AWG_BAUD)
        sine = SineWaveform()
        box = CCalibr_box(box_tty[0])
        box_eeprom = CCP210x_Eeprom("%X"%BOX_USB_VENDOR_ID, "%X"%BOX_USB_PRODUCT_ID)

        # Initialise fmc adc
        fmc_adc_init(spec, fmc)

        # Connect to AWG
        gen.connect()

        # Switch AWG output and sync OFF
        gen.output = False
        gen.sync = False

        # Measure FMC and carrier temperature
        print "SPEC temperature: %3.3f°C" % carrier.get_temp()
        print "FMC temperature : %3.3f°C" % fmc.get_temp()

        # Open all switches, reset offset DAC to mid-scale (0V)
        for channel in range(1,NB_CHANNELS+1):
            fmc.set_input_range(channel, 'OPEN')
            fmc.set_input_term(channel, 'OFF')
        fmc.dc_offset_reset()

        # Get calibration data from calibration box
        box_calibr_data = box_eeprom.get_calibr_data()
        print "\nCalibration box reference voltages:"
        for item in box_calibr_data.iteritems():
            print "%5s: %1.5fV" % (item[0], float(item[1]))

        #---------------------------------------------------------------------------
        # Get ADC and DAC offset and gain correction parameters
        #---------------------------------------------------------------------------
        print "\nRead calibration data from FMC EEPROM:"

        adc_corr_data = {'10V':{'offset':[],'gain':[],'temp':0},
                         '1V':{'offset':[],'gain':[],'temp':0},
                         '100mV':{'offset':[],'gain':[],'temp':0}}

        dac_corr_data = {'10V':{'offset':[],'gain':[],'temp':0},
                         '1V':{'offset':[],'gain':[],'temp':0},
                         '100mV':{'offset':[],'gain':[],'temp':0}}

        """
        # Read calibration data from file
        all_corr_data = []
        in_filename = CALIBR_FILENAME
        f_in = open(in_filename,"r+")
        for line in f_in:
            all_corr_data.append(int(line,16))
            print "0x%04X" % all_corr_data[-1]
        f_in.close()
        """

        # Read entire EEPROM
        print("Read all eeprom content.")
        eeprom_data_read = fmc.sys_i2c_eeprom_read(0, EEPROM_SIZE)

        # Write EEPROM data to binary file
        print("Write eeprom content to file (binary): %s"%(EEPROM_BIN_FILENAME))
        f_eeprom = open(EEPROM_BIN_FILENAME, "wb")
        for byte in eeprom_data_read:
            f_eeprom.write(chr(byte))
        f_eeprom.close()

        # Get calibration data
        #print("Extract calibration binary file to: %s"%(CALIBR_BIN_FILENAME))
        cmd = 'sdb-read -e 0x200 ' + EEPROM_BIN_FILENAME + ' calib > ' + CALIBR_BIN_FILENAME
        print("Exctract calibration binary file, cmd: %s"%(cmd))
        os.system(cmd)
        print "Get calibration data from binary file."
        calibr_data = []
        f_calibr_data = open(CALIBR_BIN_FILENAME, "rb")
        try:
            byte = f_calibr_data.read(1)
            while byte != "":
                calibr_data.append(ord(byte))
                byte = f_calibr_data.read(1)
        finally:
            f_eeprom.close()

        # Re-arrange correction data into 16-bit number (from bytes)
        eeprom_corr_data = []
        for i in range(0,len(calibr_data),2):
            eeprom_corr_data.append((calibr_data[i+1] << 8) + (calibr_data[i]))
            print "0x%04X" % eeprom_corr_data[-1]
        print "Calibration data length (16-bit): %d" % len(eeprom_corr_data)
        print "Correction data from eeprom:"

        print "\nGet ADC correction parameters:"
        for IN_RANGE in RANGES:
            for ch in range(NB_CHANNELS):
                adc_corr_data[IN_RANGE]['offset'].append(hex2signed(eeprom_corr_data.pop(0)))
            for ch in range(NB_CHANNELS):
                adc_corr_data[IN_RANGE]['gain'].append(eeprom_corr_data.pop(0))
            adc_corr_data[IN_RANGE]['temp'] = eeprom_corr_data.pop(0)/100.0

        for ranges in adc_corr_data.iteritems():
            print "%s:" % ranges[0]
            for corr in ranges[1].iteritems():
                print " - %6s: " % corr[0],
                if type(corr[1]) is list:
                    for val in corr[1]:
                        print "%6d " % val,
                else:
                    print "%2.3f " % corr[1],
                print ""
            print ""

        print "\nGet DAC correction parameters:"
        for IN_RANGE in RANGES:
            for ch in range(NB_CHANNELS):
                dac_corr_data[IN_RANGE]['offset'].append(hex2signed(eeprom_corr_data.pop(0)))
            for ch in range(NB_CHANNELS):
                dac_corr_data[IN_RANGE]['gain'].append(eeprom_corr_data.pop(0))
            dac_corr_data[IN_RANGE]['temp'] = eeprom_corr_data.pop(0)/100.0

        for ranges in dac_corr_data.iteritems():
            print "%s:" % ranges[0]
            for corr in ranges[1].iteritems():
                print " - %6s: " % corr[0],
                if type(corr[1]) is list:
                    for val in corr[1]:
                        print "%6d " % val,
                else:
                    print "%2.3f " % corr[1],
                print ""
            print ""

        #---------------------------------------------------------------------------
        # Write DAC gain and offset correction value to fmc class
        #---------------------------------------------------------------------------
        print "\nApply DAC correction\n"
        fmc.set_dac_corr(dac_corr_data)

        #---------------------------------------------------------------------------
        # ADC Calibration verification
        #---------------------------------------------------------------------------
        error = 0

        for IN_RANGE in RANGES:

            print "\n--------------------------------------------------------------------------------"
            print "ADC calibration verification for %s input range\n----------------------------------"%(IN_RANGE)

            print "\nApply ADC offset correction\n"
            #fmc.print_adc_core_config()
            for ch in range(NB_CHANNELS):
                fmc.set_adc_gain_offset_corr(ch+1, adc_corr_data[IN_RANGE]['gain'][ch], adc_corr_data[IN_RANGE]['offset'][ch])
            #fmc.print_adc_core_config()

            #for ch in range(NB_CHANNELS):
            #    print "CH%d ADC offset correction write:0x%.8X read:0x%.8X"%(ch+1,adc_corr_data[IN_RANGE]['offset'][ch],fmc.get_adc_offset_corr(ch+1))
            #    print "CH%d ADC gain correction   write:0x%.8X read:0x%.8X"%(ch+1,adc_corr_data[IN_RANGE]['gain'][ch],fmc.get_adc_gain_corr(ch+1))

            print "SPEC temperature: %3.3f°C" % carrier.get_temp()
            print "FMC temperature : %3.3f°C" % fmc.get_temp()

            print ""
            for ch in range(1,NB_CHANNELS+1):
                print "Channel %d ADC gain correction register  : 0x%04X" % (ch, fmc.get_adc_gain_corr(ch))
                print "Channel %d ADC offset correction register: 0x%04X" % (ch, fmc.get_adc_offset_corr(ch))

            # Input and offset DAC volatges for the measurements
            vref = float(box_calibr_data[IN_RANGE]) # from box cailbration data in CP2103 EEPROM
            v_in  = [0.0, vref]
            v_dac = 0.0

            # Make the measurements
            v_meas = []
            d_meas = []
            for i in range(len(v_in)):
                print "\nMeasurement %d: channel input = %2.8fV, offset DAC = %2.8fV"%((i+1), v_in[i], v_dac)
                mean, diff = make_meas(carrier, fmc, box, v_dac, v_in[i], IN_RANGE, ADC_FS[IN_RANGE], REPEAT)
                v_meas.append(mean)
                d_meas.append(diff)
                max_ripple = MAX_RIPPLE_LSB * ADC_LSB[IN_RANGE]
                print "   Check voltage stability (reference voltage ripple):"
                print "   Maximum allowed ripple = %d LSB = %1.9f[V]" % (MAX_RIPPLE_LSB, max_ripple)
                for ch in range(1,NB_CHANNELS+1):
                    ripple = d_meas[i][ch-1]
                    print "Channel %d: v_meas=%2.9f[V], ripple=%1.9f[V] => "%(ch, v_meas[i][ch-1], d_meas[i][ch-1]),
                    if abs(ripple) > max_ripple:
                        print "ERROR                    ###"
                        error += 1
                    else:
                        print "OK"

            # Make verification
            print "\nVerification"
            print "Maximum offset error: %2.4f%% FS" % MAX_OFFSET_ERROR
            print "Maximum gain error  : %2.4f%% FS" % MAX_GAIN_ERROR
            for ch in range(1,NB_CHANNELS+1):
                print "Channel %d"%(ch)
                # Offset error
                offset_err = v_meas[0][ch-1]
                # Offset error in % FS
                offset_err_pfs = (100.0/ADC_FS[IN_RANGE]) * abs(offset_err)
                # Calculate maximum offset error
                max_offset_err = ADC_FS[IN_RANGE] * (MAX_OFFSET_ERROR/100.0)
                print "   Offset error:%1.9f[V] (%2.4f%% FS), max allowed:+/-%1.5f[V] => " % (offset_err, offset_err_pfs, max_offset_err),
                if abs(offset_err) > max_offset_err:
                    print "ERROR                    ###"
                    error += 1
                else:
                    print "OK"
                # Calculate gain error
                gain_err = (v_meas[1][ch-1] - offset_err) - (v_in[1])
                # Gain error in % FS
                gain_err_pfs = (100.0/ADC_FS[IN_RANGE]) * abs(gain_err)
                # Calculate maximum gain error
                max_gain_err = ADC_FS[IN_RANGE] * (MAX_GAIN_ERROR/100.0)
                print "   Gain error  :%1.9f[V] (%2.4f%% FS), max allowed:+/-%1.5f[V] => " % (gain_err, gain_err_pfs, max_gain_err),
                if abs(gain_err) > max_gain_err:
                    print "ERROR                    ###"
                    error += 1
                else:
                    print "OK"

        #---------------------------------------------------------------------------
        # DAC Calibration verification
        #---------------------------------------------------------------------------
        for IN_RANGE in RANGES:

            print "\n--------------------------------------------------------------------------------"
            print "DAC calibration verification for %s input range\n----------------------------------"%(IN_RANGE)

            print "\nApply ADC offset correction\n"
            #fmc.print_adc_core_config()
            for ch in range(NB_CHANNELS):
                fmc.set_adc_gain_offset_corr(ch+1, adc_corr_data[IN_RANGE]['gain'][ch], adc_corr_data[IN_RANGE]['offset'][ch])
            #fmc.print_adc_core_config()

            #for ch in range(NB_CHANNELS):
            #    print "CH%d ADC offset correction write:0x%.8X read:0x%.8X"%(ch+1,adc_corr_data[IN_RANGE]['offset'][ch],fmc.get_adc_offset_corr(ch+1))
            #    print "CH%d ADC gain correction   write:0x%.8X read:0x%.8X"%(ch+1,adc_corr_data[IN_RANGE]['gain'][ch],fmc.get_adc_gain_corr(ch+1))

            print "SPEC temperature: %3.3f°C" % carrier.get_temp()
            print "FMC temperature : %3.3f°C" % fmc.get_temp()

            print ""
            for ch in range(1,NB_CHANNELS+1):
                print "Channel %d ADC gain correction register  : 0x%04X" % (ch, fmc.get_adc_gain_corr(ch))
                print "Channel %d ADC offset correction register: 0x%04X" % (ch, fmc.get_adc_offset_corr(ch))

            # Input and offset DAC volatges for the measurements
            vref = float(box_calibr_data[IN_RANGE]) # from box cailbration data in CP2103 EEPROM
            v_in  = 0.0
            v_dac = [0.0, vref]
            #v_dac = [0.0, vref, vref/10, -vref]

            # Make the measurements
            v_meas = []
            d_meas = []
            for i in range(len(v_dac)):
                print "\nMeasurement %d: channel input = %2.8fV, offset DAC = %2.8fV"%((i+1), v_in, v_dac[i])
                mean, diff = make_meas(carrier, fmc, box, v_dac[i], v_in, IN_RANGE, ADC_FS[IN_RANGE], REPEAT)
                v_meas.append(mean)
                d_meas.append(diff)
                max_ripple = MAX_RIPPLE_LSB * ADC_LSB[IN_RANGE]
                print "   Check voltage stability (reference voltage ripple):"
                print "   Maximum allowed ripple = %d LSB = %1.9f[V]" % (MAX_RIPPLE_LSB, max_ripple)
                for ch in range(1,NB_CHANNELS+1):
                    ripple = d_meas[i][ch-1]
                    print "Channel %d: v_meas=%02.9f[V], ripple=%1.9f[V] => "%(ch, v_meas[i][ch-1], d_meas[i][ch-1]),
                    if abs(ripple) > max_ripple:
                        print "ERROR                    ###"
                        error += 1
                    else:
                        print "OK"

            # Make verification
            print "\nVerification"
            print "Maximum offset error: %2.4f%% FS" % MAX_OFFSET_ERROR
            print "Maximum gain error  : %2.4f%% FS" % MAX_GAIN_ERROR
            for ch in range(1,NB_CHANNELS+1):
                print "Channel %d"%(ch)
                # Offset error
                offset_err = v_meas[0][ch-1]
                # Offset error in % FS
                offset_err_pfs = (100.0/ADC_FS[IN_RANGE]) * abs(offset_err)
                # Calculate maximum offset error
                max_offset_err = ADC_FS[IN_RANGE] * (MAX_OFFSET_ERROR/100.0)
                print "   Offset error:%1.9f[V] (%2.4f%% FS), max allowed:+/-%1.5f[V] => "%(offset_err, offset_err_pfs, max_offset_err),
                if abs(offset_err) > max_offset_err:
                    print "ERROR                    ###"
                    error += 1
                else:
                    print "OK"
                # Calculate the actual value of the DAC
                dac_d = volt2digital(v_dac[i],DAC_FS,DAC_NBITS)
                dac_r = digital2volt(dac_d,DAC_FS,DAC_NBITS)
                # Calculate gain error (note that the ADC input is inversely proportional to the DAC output)
                gain_err = (v_meas[1][ch-1] - offset_err) + (dac_r)
                # Gain error in % FS
                gain_err_pfs = (100.0/ADC_FS[IN_RANGE]) * abs(gain_err)
                # Calculate maximum gain error
                max_gain_err = ADC_FS[IN_RANGE] * (MAX_GAIN_ERROR/100.0)
                print "   Gain error  :%1.9f[V] (%2.4f%% FS), max allowed:+/-%1.5f[V] => "%(gain_err, gain_err_pfs, max_gain_err),
                if abs(gain_err) > max_gain_err:
                    print "ERROR                    ###"
                    error += 1
                else:
                    print "OK"


        # Open all switches, reset offset DAC to mid-scale (0V)
        for channel in range(1,NB_CHANNELS+1):
            fmc.set_input_range(channel, 'OPEN')
            fmc.set_input_term(channel, 'OFF')
        fmc.dc_offset_reset()

        # Close AWG
        gen.close()

    except(FmcAdc100mSpecOperationError,FmcAdc100mOperationError,
           CalibrBoxOperationError, CP210xEepromOperationError) as e:
        raise PtsError("Test failed: %s" % e)


    print ""
    print "==> End of test%02d" % TEST_NB
    print "================================================================================"
    end_test_time = time.time()
    print "Test%02d elapsed time: %.2f seconds\n" % (TEST_NB, end_test_time-start_test_time)

    # Check if an error occured during calibration verification
    if(error != 0):
        raise PtsError('An error occured during calibration verification, check log for details.')


if __name__ == '__main__' :
    main()
