#!   /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

import sys
import rr
import time
import os
from numpy import *
from pylab import *
from ctypes import *

from ptsexcept import *

import spec_fmc_adc
import fmc_adc
import calibr_box
import find_usb_tty
import cp210x_eeprom
from PAGE.Agilent33250A import *
from PAGE.SineWaveform import *


"""
test20: Calibration verification

Note: Requires test00.py to run first to load the firmware!
"""

# 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

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


def load_firmware(default_directory):
    print "Load firmware to FPGA"
    path_fpga_loader = '../../../gnurabbit/user/fpga_loader';
    path_firmware = '../firmwares/spec_fmcadc100m14b4cha.bin';
    firmware_loader = os.path.join(default_directory, path_fpga_loader)
    bitstream = os.path.join(default_directory, path_firmware)
    print firmware_loader + ' ' + bitstream
    os.system( firmware_loader + ' ' + bitstream )
    time.sleep(2);

def disconnect_channels(fmc):
    for i in range(1,NB_CHANNELS+1):
        fmc.set_ssr(i, 0x00)
        time.sleep(SSR_SET_SLEEP)

def fmc_adc_init(spec, fmc):
    print "\nInitialise 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)

def acq_channel(spec_fmc, 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" iinterrupt
    spec_fmc.set_irq_en_mask(0x1)
    # Read ACQ_LENGTH samples after the trigger for all channels
    channels_data = spec_fmc.get_data((trig_pos<<3), ACQ_LENGTH*8)
    # Disable "DMA done" iinterrupt
    spec_fmc.set_irq_en_mask(0x0)
    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)

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)

def make_meas(spec_fmc, 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(spec_fmc, 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 plot_all(data, mean, ylimit):
    sample = arange(len(data)/4)
    clf()
    plot(sample, data[0::4], 'b', label='Channel 1')
    plot(sample, data[1::4], 'g', label='Channel 2')
    plot(sample, data[2::4], 'c', label='Channel 3')
    plot(sample, data[3::4], 'm', label='Channel 4')
    plot(sample, [mean[0]]*len(sample), 'r')
    plot(sample, [mean[1]]*len(sample), 'r')
    plot(sample, [mean[2]]*len(sample), 'r')
    plot(sample, [mean[3]]*len(sample), 'r')
    ylim(-ylimit-(ylimit/10.0), ylimit+(ylimit/10.0))
    grid(which='both')
    legend()
    draw()
    show()
    return 0


def main (default_directory = '.'):

    # Load firmware to FPGA
    load_firmware(default_directory)

    # Objects declaration
    spec = rr.Gennum() # bind to the SPEC board
    spec_fmc = spec_fmc_adc.CSpecFmcAdc100Ms(spec)
    fmc = fmc_adc.CFmcAdc100Ms(spec)
    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)
    #print "AWG:%s"%awg_tty[0]
    #print "BOX:%s"%box_tty[0]
    gen = Agilent33250A(device=awg_tty[0], bauds=AWG_BAUD)
    sine = SineWaveform()
    box = calibr_box.CCalibr_box(box_tty[0])
    box_eeprom = cp210x_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" % spec_fmc.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 data:"
    print box_calibr_data


    #---------------------------------------------------------------------------
    # Get ADC offset and gain correction parameters
    #---------------------------------------------------------------------------
    print "\n Get ADC correctioon parameters."

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

    in_filename = "test19_corr_data.txt"
    f_in = open(in_filename,"r+")
    for line in f_in:
        data,typelen=line.split(',')
        all_corr_data.append(int(data,16))
    print all_corr_data
    f_in.close()

    for IN_RANGE in RANGES:
        for ch in range(NB_CHANNELS):
            adc_corr_data[IN_RANGE]['offset'].append(hex2signed(all_corr_data.pop(0)))
        for ch in range(NB_CHANNELS):
            adc_corr_data[IN_RANGE]['gain'].append(all_corr_data.pop(0))

    print adc_corr_data

    #---------------------------------------------------------------------------
    # Get DAC offset and gain correction parameters
    #---------------------------------------------------------------------------
    print "\n Get DAC correctioon parameters."

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

    in_filename = "test19_dac_corr_data.txt"
    f_in = open(in_filename,"r+")
    for line in f_in:
        data,typelen=line.split(',')
        all_corr_data.append(int(data,16))
    print all_corr_data
    f_in.close()

    for IN_RANGE in RANGES:
        for ch in range(NB_CHANNELS):
            dac_corr_data[IN_RANGE]['offset'].append(hex2signed(all_corr_data.pop(0)))
        for ch in range(NB_CHANNELS):
            dac_corr_data[IN_RANGE]['gain'].append(all_corr_data.pop(0))

    print dac_corr_data


    #---------------------------------------------------------------------------
    # Write DAC gain and offset corerection 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 "\nADC 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" % spec_fmc.get_temp()
        print "FMC temperature : %3.3f°C" % fmc.get_temp()

        # 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(spec_fmc, fmc, box, v_dac, v_in[i], IN_RANGE, ADC_FS[IN_RANGE], REPEAT)
            v_meas.append(mean)
            d_meas.append(diff)
            for ch in range(1,NB_CHANNELS+1):
                print "Channel %d: v_meas=%02.9fV delta=%1.9f"%(ch, v_meas[i][ch-1], d_meas[i][ch-1])
                print "   Check voltage stability"
                meas = d_meas[i][ch-1]
                expect = (2*ADC_LSB[IN_RANGE])
                if meas > expect:
                    print "      ERROR: measured:%1.9f max allowed:%1.9f"%(meas, expect)
                    error += 1
                else:
                    print "      OK   : measured:%1.9f max allowed:%1.9f"%(meas, expect)

        print "\n"

        for ch in range(1,NB_CHANNELS+1):
            print "Channel %d"%(ch)
            print "   Check 0V offset"
            meas = (v_meas[0][ch-1] - v_in[0])
            expect = (2*ADC_LSB[IN_RANGE])
            if meas > expect:
                print "      ERROR: measured:%1.9f max allowed:%1.9f"%(meas, expect)
                error += 1
            else:
                print "      OK   : measured:%1.9f max allowed:%1.9f"%(meas, expect)
            print "   Check gain (difference with gain=1)"
            # Offset error
            off_err = v_meas[0][ch-1]
            # Calculate gain
            gain = (v_meas[1][ch-1] - off_err) / (v_in[1])
            # Calculate the difference with a gain of 1
            meas = abs(1 - gain)
            # Calculate maximum difference with gain of 1
            expect = abs(ADC_FS[IN_RANGE]/100.0)
            if meas > expect:
                print "      ERROR: gain:%1.9f diff:%1.9f max allowed:%1.9f"%(gain, meas, expect)
                error += 1
            else:
                print "      OK   : gain:%1.9f diff:%1.9f max allowed:%1.9f"%(gain, meas, expect)

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

        print "\nDAC 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" % spec_fmc.get_temp()
        print "FMC temperature : %3.3f°C" % fmc.get_temp()

        # 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 = []
        off_err = []
        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(spec_fmc, fmc, box, v_dac[i], v_in, IN_RANGE, ADC_FS[IN_RANGE], REPEAT)
            v_meas.append(mean)
            d_meas.append(diff)
            for ch in range(1,NB_CHANNELS+1):
                print "Channel %d: v_meas=%02.9fV delta=%1.9f"%(ch, v_meas[i][ch-1], d_meas[i][ch-1])
                print "   Check voltage stability"
                meas = d_meas[i][ch-1]
                expect = (2*ADC_LSB[IN_RANGE])
                if meas > expect:
                    print "      ERROR: measured:%1.9f max allowed:%1.9f"%(meas, expect)
                    error += 1
                else:
                    print "      OK   : measured:%1.9f max allowed:%1.9f"%(meas, expect)
                if v_dac[i] != 0.0:
                    # 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
                    gain = (v_meas[i][ch-1] - off_err[ch-1]) / -(dac_r)
                    # Calculate the difference with a gain of 1
                    meas = abs(1 - gain)
                    # Calculate maximum difference with gain of 1
                    expect = abs(ADC_FS[IN_RANGE]/100.0)
                    print "   Check gain (difference with gain=1)"
                    print "   offset error:%1.9f"%off_err[ch-1]
                    if meas > expect:
                        print "      ERROR: gain:%1.9f diff:%1.9f max allowed:%1.9f"%(gain, meas, expect)
                        error += 1
                    else:
                        print "      OK   : gain:%1.9f diff:%1.9f max allowed:%1.9f"%(gain, meas, expect)
                else:
                    off_err.append(v_meas[i][ch-1])
                    print "   Offset error:%2.9f"%(off_err[ch-1])


    # 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()

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


if __name__ == '__main__' :
    main()
