Esempio n. 1
0
class StarLord(MIXBoard):
    '''
    StarLord is a high resolution differential input digital audio analyzer module

    Args:
        i2c:    instance(I2C), the instance of I2C bus. which will be used to used
                               to control eeprom, sensor and io expander.
        ipcore: instance(MIXAUT5SGR), the instance of MIXAUT5SGR, which include
                                    AD717x, FFT Analyzer, Signal Source and gpio
                                    function. If device name string is passed
                                    to the parameter, the ipcore can be instanced
                                    in the module.

    Examples:
        i2c = I2C('/dev/i2c-0')
        starlord = StarLord(i2c, '/dev/MIX_AUT5_SG_R_0')
        starlord.module_init()
        # measure left channel input
        result = starlord.measure('left', 20000, 3)
        print("vpp={}, freq={}, thd={}, thdn={}, rms={}, noisefloor={}".format(result['vpp'],
              result['freq'], result['thd'], result['thdn'], result['rms'],
              result['noisefloor']))

        # measure LNA signal
        result = starlord.measure('left', '50mV', 20000, 3, 1000)
        print("vpp={}, freq={}, thd={}, thdn={}, rms={}".format(result['vpp'],
              result['freq'], result['thd'], result['thdn'], result['rms']))
    '''
    compatible = ['GQQ-AUD003002-000']

    rpc_public_api = [
        'module_init', 'enable_upload', 'disable_upload', 'measure',
        'enable_output', 'disable_output', 'measure_lna', 'enable_lna_upload',
        'disable_lna_upload', 'config_lna_scope'
    ] + MIXBoard.rpc_public_api

    def __init__(self, i2c, ipcore=None, range_table=starlord_table):
        if i2c:
            self.eeprom = CAT24C32(StarLordDef.EEPROM_I2C_ADDR, i2c)
            self.nct75 = NCT75(StarLordDef.TEMP_I2C_ADDR, i2c)
            self.pca9536 = PCA9536(StarLordDef.PCA9536_DEV_ADDR, i2c)

        else:
            self.eeprom = EepromEmulator("cat24cxx_emulator")
            self.nct75 = NCT75Emulator("nct75_emulator")
            self.pca9536 = PCA9536Emulator("PCA9536_emulator")

        if ipcore:
            if isinstance(ipcore, basestring):
                ipcore = MIXAUT5SGR(ipcore)
            self.ipcore = ipcore
            self.analyzer = self.ipcore.analyzer
            self.signal_source = self.ipcore.signal_source
            self.ad7175 = self.ipcore.ad717x
            self.ad7175.config = {
                'ch0': {
                    'P': 'AIN0',
                    'N': 'AIN1'
                },
                'ch1': {
                    'P': 'AIN2',
                    'N': 'AIN3'
                }
            }
            self.adc_rst_pin = Pin(self.ipcore.gpio, StarLordDef.ADC_RESET_PIN)
            self.i2s_rx_en_pin = Pin(self.ipcore.gpio,
                                     StarLordDef.I2S_RX_EN_PIN)
            self.dac_rst_pin = Pin(self.ipcore.gpio, StarLordDef.DAC_RESET_PIN)
            self.i2s_tx_en_pin = Pin(self.ipcore.gpio,
                                     StarLordDef.I2S_TX_EN_PIN)
            self.i2s_ch_select = [
                Pin(self.ipcore.gpio, StarLordDef.I2S_CH_SELECT_2),
                Pin(self.ipcore.gpio, StarLordDef.I2S_CH_SELECT_3)
            ]
            self.fft_source_select = Pin(self.ipcore.gpio,
                                         StarLordDef.FFT_SOURCE_SELECT)
            self.ad7175_upload_select = Pin(self.ipcore.gpio,
                                            StarLordDef.AD7175_TO_FFT_OR_NOT)

        else:
            self.analyzer = MIXFftAnalyzerSGEmulator(
                "mix_fftanalyzer_sg_emulator")
            self.signal_source = MIXSignalSourceSGEmulator(
                "mix_signalsource_sg_emulator")
            self.adc_rst_pin = Pin(None, StarLordDef.ADC_RESET_PIN)
            self.i2s_rx_en_pin = Pin(None, StarLordDef.I2S_RX_EN_PIN)
            self.dac_rst_pin = Pin(None, StarLordDef.DAC_RESET_PIN)
            self.i2s_tx_en_pin = Pin(None, StarLordDef.I2S_TX_EN_PIN)
            self.ad7175 = MIXAd7175SGEmulator('mix_ad7175_sg_emulator',
                                              StarLordDef.EMULATOR_REG_SIZE)
            self.i2s_ch_select = [
                Pin(None, StarLordDef.I2S_CH_SELECT_2),
                Pin(None, StarLordDef.I2S_CH_SELECT_3)
            ]
            self.fft_source_select = Pin(None, StarLordDef.FFT_SOURCE_SELECT)
            self.ad7175_upload_select = Pin(None,
                                            StarLordDef.AD7175_TO_FFT_OR_NOT)

        super(StarLord, self).__init__(self.eeprom,
                                       self.nct75,
                                       cal_table={},
                                       range_table=range_table)
        self.is_lna_up = False
        self.is_analyzer_up = False
        self.is_enable_upload = False

    def module_init(self):
        '''
        Init module, which will reset dac/adc, i2s module and load calibration

        Returns:
            string, "done", execution successful.

        Examples:
            starloard.module_init()
        '''
        self.adc_rst_pin.set_dir(StarLordDef.IO_DIR_OUTPUT)
        self.dac_rst_pin.set_dir(StarLordDef.IO_DIR_OUTPUT)
        self.i2s_rx_en_pin.set_dir(StarLordDef.IO_DIR_OUTPUT)
        self.i2s_tx_en_pin.set_dir(StarLordDef.IO_DIR_OUTPUT)
        self.i2s_ch_select[StarLordDef.AUDIO_CHANNEL_SELECT_BIT0].set_dir(
            StarLordDef.IO_DIR_OUTPUT)
        self.i2s_ch_select[StarLordDef.AUDIO_CHANNEL_SELECT_BIT1].set_dir(
            StarLordDef.IO_DIR_OUTPUT)
        self.fft_source_select.set_dir(StarLordDef.IO_DIR_OUTPUT)
        self.ad7175_upload_select.set_dir(StarLordDef.IO_DIR_OUTPUT)

        # reset ADC
        self.adc_rst_pin.set_level(0)
        time.sleep(StarLordDef.RELAY_DELAY_S)
        self.adc_rst_pin.set_level(1)

        # reset DAC
        self.dac_rst_pin.set_level(0)
        time.sleep(StarLordDef.RELAY_DELAY_S)
        self.dac_rst_pin.set_level(1)

        # reset i2s rx
        self.i2s_rx_en_pin.set_level(0)

        # reset i2s tx
        self.i2s_tx_en_pin.set_level(0)

        # io init
        self.pca9536.set_pin_dir(StarLordDef.AD7175_CH_LEFT_CTL_BIT,
                                 StarLordDef.IO_DIR_OUTPUT)
        self.pca9536.set_pin_dir(StarLordDef.AD7175_CH_RIGHT_CTL_BIT,
                                 StarLordDef.IO_DIR_OUTPUT)

        self.pca9536.set_pin(StarLordDef.AD7175_CH_LEFT_CTL_BIT, 0)
        self.pca9536.set_pin(StarLordDef.AD7175_CH_RIGHT_CTL_BIT, 0)

        # ad7175 init
        self.ad7175.channel_init()

        # measure lna config init
        self.config_lna_scope(StarLordDef.CH_LEFT, StarLordDef.LNA_RANGE_5V)

        self.load_calibration()

        return "done"

    def enable_upload(self):
        '''
        Enable module data upload.

        Returns:
            string, "done", execution successful.
        '''
        self.i2s_rx_en_pin.set_level(StarLordDef.I2S_RX_ENABLE)
        self.analyzer.enable_upload()
        self.is_enable_upload = True

        return "done"

    def disable_upload(self):
        '''
        Disable module data upload.

        Returns:
            string, "done", execution successful.
        '''
        self.analyzer.disable_upload()
        self.i2s_rx_en_pin.set_level(StarLordDef.I2S_RX_DISABLE)
        self.is_enable_upload = False

        return "done"

    def measure(self,
                channel,
                bandwidth_hz,
                harmonic_count,
                decimation_type=0xFF,
                sampling_rate=StarLordDef.AUDIO_SAMPLING_RATE):
        '''
        Measure audio input signal, which captures data using CS5361.

        Args:
            channel:         string, ['left', 'right'], select input signal channel.
            bandwidth_hz:    int/string, [42~48000], unit Hz, the signal bandwidth.
            harmonic_count:  int, [2~10], The harmonic count of signal.
            decimation_type: int, [1~255], default 0xFF, sample data decimation.
            sampling_rate:   int, [1~192000], default 192000, unit Hz, ADC sampling rate.

        Returns:
            dict, {'vpp': value, 'freq': value, 'thd': value, 'thdn': value, 'rms': value, 'noisefloor': value},
            measurement result.
        '''
        assert channel in StarLordDef.AUDIO_CHANNEL_LIST

        if self.is_enable_upload is False:
            self.i2s_rx_en_pin.set_level(StarLordDef.I2S_RX_ENABLE)

        pin = self.i2s_ch_select[StarLordDef.AUDIO_CHANNEL_SELECT_BIT0]
        pin.set_level(StarLordDef.AUDIO_CHANNEL_LIST[channel][
            StarLordDef.AUDIO_CHANNEL_SELECT_BIT0])
        pin = self.i2s_ch_select[StarLordDef.AUDIO_CHANNEL_SELECT_BIT1]
        pin.set_level(StarLordDef.AUDIO_CHANNEL_LIST[channel][
            StarLordDef.AUDIO_CHANNEL_SELECT_BIT1])

        self.fft_source_select.set_level(StarLordDef.FFT_SOURCE_FROM_CS5361)

        result = self._analyzer(sampling_rate, decimation_type, bandwidth_hz,
                                harmonic_count)

        range_name = "AUDIO_CS5361_RMS_" + channel

        result['rms'] = (self.calibrate(range_name, result['rms'][0]),
                         StarLordDef.VOLT_UNIT_RMS)

        if self.is_enable_upload is False:
            self.i2s_rx_en_pin.set_level(StarLordDef.I2S_RX_DISABLE)

        return result

    def config_lna_scope(self,
                         channel,
                         scope,
                         adc_upload_ch=StarLordDef.AD7175_UPLOAD_TO_FFT):
        '''
        Config LNA measurement scope

        Args:
            channel:        string, ['left', 'right'], the channel to be upload.
            scope:          string, ['5V', '50mV'], AD7175 measurement range.
            adc_upload_ch:  string, ['dma', 'fft'], default 'fft', AD7175 source data is uploaded to DMA or FFT.

        Returns:
            string, "done",  execution successful.
        '''
        assert channel in StarLordDef.AD7175_CH_LIST
        assert scope in StarLordDef.LNA_SCOPE
        assert adc_upload_ch in StarLordDef.AD7175_UPLOAD_CH

        self.channel = channel
        self.scope = scope
        if scope == StarLordDef.LNA_RANGE_5V:
            self.pca9536.set_pin(StarLordDef.AD7175_CH_LIST[channel],
                                 StarLordDef.SEL_GAIN_1)
        else:
            self.pca9536.set_pin(StarLordDef.AD7175_CH_LIST[channel],
                                 StarLordDef.SEL_GAIN_100)

        self.ad7175_upload_select.set_level(
            StarLordDef.AD7175_UPLOAD_CH[adc_upload_ch])

        return "done"

    def enable_lna_upload(self,
                          channel,
                          sampling_rate=StarLordDef.LNA_SAMPLING_RATE):
        '''
        Enable LNA adc data upload

        Args:
            channel:        string, ['left', 'right'], the channel to be upload.
            sampling_rate:  float, default 250000, unit Hz, AD7175 sampling rate,
                                   please refer to datasheet for more information.

        Returns:
            string, "done",  execution successful.
        '''
        assert channel in StarLordDef.AD7175_CH_LIST

        self.is_lna_up = True
        self.ad7175.disable_continuous_sampling(
            StarLordDef.AD7175_CH_LIST[channel])
        time.sleep(StarLordDef.RELAY_DELAY_S)

        self.ad7175.enable_continuous_sampling(
            StarLordDef.AD7175_CH_LIST[channel], sampling_rate)

        return "done"

    def disable_lna_upload(self, channel):
        '''
        Disable LNA adc data upload

        Args:
            channel:        string, ['left', 'right'], the channel to be upload.

        Returns:
            string, "done", execution successful.
        '''
        assert channel in StarLordDef.AD7175_CH_LIST

        self.is_lna_up = False
        if self.is_analyzer_up is True:
            self.analyzer.disable_upload()
            self.is_analyzer_up = False

        self.ad7175.disable_continuous_sampling(
            StarLordDef.AD7175_CH_LIST[channel])

        return "done"

    def measure_lna(self,
                    bandwidth_hz,
                    harmonic_count,
                    decimation_type=0xFF,
                    sampling_rate=StarLordDef.LNA_SAMPLING_RATE):
        '''
        Measure audio LNA input signal, which captures data using AD7175.

        Args:
            bandwidth_hz:    int/string, [42~48000], unit Hz, the signal bandwidth.
            harmonic_count:  int, [2~10], The harmonic count of signal.
            decimation_type: int, [1~255], default 0xFF, sample data decimation.
            sampling_rate:   int, [5~250000], default 250000, unit Hz,
                                  Sample rate of your ADC device.

        Returns:
            dict, {'vpp': value, 'freq': value, 'thd': value, 'thdn': value, 'rms': value, 'noisefloor': value},
            measurement result.
        '''
        self.fft_source_select.set_level(StarLordDef.FFT_SOURCE_FROM_AD7175)

        if self.is_lna_up is True and self.is_analyzer_up is False:
            self.analyzer.enable_upload()
            self.is_analyzer_up = True

        self.analyzer.disable()
        self.analyzer.enable()
        self.analyzer.analyze_config(sampling_rate, decimation_type,
                                     bandwidth_hz, harmonic_count)
        self.analyzer.analyze()

        range_name = "LNA_" + self.scope + "_" + self.channel
        gain = StarLordDef.GAIN_VALUE[self.scope]
        vpp = self.analyzer.get_vpp() * gain
        rms = vpp / StarLordDef.RMS_TO_VPP_RATIO
        rms = self.calibrate(range_name, rms)
        thdn_value = self.analyzer.get_thdn()

        result = dict()
        result["vpp"] = (vpp, StarLordDef.VOLT_UNIT_MV)
        result["freq"] = (self.analyzer.get_frequency(),
                          StarLordDef.FREQ_UNIT_HZ)
        result["thd"] = (self.analyzer.get_thd(), StarLordDef.THD_UNIT_DB)
        result["thdn"] = (thdn_value, StarLordDef.THDN_UNIT_DB)
        result["rms"] = (rms, StarLordDef.VOLT_UNIT_RMS)
        result["noisefloor"] = (10**(thdn_value / 20) * rms,
                                StarLordDef.VOLT_UNIT_RMS)

        return result

    def enable_output(self, freq, vpp):
        '''
        StarLord CS5361 output audio sine waveform.

        Args:
            freq:       int, [5~50000], unit Hz, output signal's frequency.
            vpp:        float, [0~6504], unit mV, output signal's vpp.

        Returns:
            string, "done", execution successful.
        '''
        assert StarLordDef.OUTPUT_FREQ_MIN <= freq
        assert freq <= StarLordDef.OUTPUT_FREQ_MAX
        assert StarLordDef.OUTPUT_VPP_MIN <= vpp
        assert vpp <= StarLordDef.OUTPUT_VPP_MAX

        vpp = self.calibrate(StarLordDef.OUTPUT_CAL_ITEM, vpp)
        vpp = 0 if vpp < 0 else vpp
        # enable I2S tx module
        self.i2s_tx_en_pin.set_level(StarLordDef.AUDIO_OUTPUT_ENABLE)

        self.signal_source.close()
        self.signal_source.open()
        # calculate vpp to vpp scale for FPGA
        vpp_scale = vpp * StarLordDef.VPP_2_SCALE_RATIO
        self.signal_source.set_swg_paramter(StarLordDef.AUDIO_SAMPLING_RATE,
                                            freq, vpp_scale,
                                            StarLordDef.OUTPUT_SIGNAL_DUTY)
        self.signal_source.set_signal_type(StarLordDef.OUTPUT_WAVE)
        self.signal_source.set_signal_time(StarLordDef.SIGNAL_ALWAYS_OUTPUT)
        self.signal_source.output_signal()

        return "done"

    def disable_output(self):
        '''
        Disable Cs5361 output signal.

        Returns:
            string, "done", execution successful.
        '''
        self.signal_source.close()
        self.i2s_tx_en_pin.set_level(StarLordDef.AUDIO_OUTPUT_DISABLE)

        return "done"

    def _analyzer(self, sampling_rate, decimation_type, bandwidth_hz,
                  harmonic_count):
        '''
        Measure audio input signal.

        Returns:
            dict, {'vpp': value, 'freq': value, 'thd': value, 'thdn': value, 'rms': value, 'noisefloor': value},
            measurement result.
        '''
        self.analyzer.disable()
        self.analyzer.enable()
        self.analyzer.analyze_config(sampling_rate, decimation_type,
                                     bandwidth_hz, harmonic_count)
        self.analyzer.analyze()

        # calculate the actual vpp of HW by VREF
        vpp = self.analyzer.get_vpp() * StarLordDef.AUDIO_ANALYZER_VREF
        # vpp = RMS * 2 * sqrt(2)
        rms = vpp / StarLordDef.RMS_TO_VPP_RATIO
        vpp = rms * StarLordDef.RMS_TO_VPP_RATIO
        thdn_value = self.analyzer.get_thdn()

        result = dict()
        result["vpp"] = (vpp, StarLordDef.VOLT_UNIT_MV)
        result["freq"] = (self.analyzer.get_frequency(),
                          StarLordDef.FREQ_UNIT_HZ)
        result["thd"] = (self.analyzer.get_thd(), StarLordDef.THD_UNIT_DB)
        result["thdn"] = (thdn_value, StarLordDef.THDN_UNIT_DB)
        result["rms"] = (rms, StarLordDef.VOLT_UNIT_RMS)
        result["noisefloor"] = (10**(thdn_value / 20) * rms,
                                StarLordDef.VOLT_UNIT_RMS)

        return result
Esempio n. 2
0
class Wolverine(SGModuleDriver):
    '''
    Wolverine is a compact version of the digital multimeter which internal ADC resolution is 24 bit.

    It can be used as
    high performance DMM to measure DC voltage and small signal DC current. In this class, adc channel 0 is current
    channel, adc channel 1 is voltage channel. DMM001 support signal measurement and continuous measurement. Note that
    if range_ctrl0 and range_ctrl1 not given, internal submodule GPIO device of MIX_DAQT1 will be used to control range.
    Note that calibration default is enabled. This class is legacy driver for normal boot.

    Args:
        i2c:             instance(I2C)/None,   which is used to control nct75 and cat24c32. If not given,
                                               emulator will be created.
        ad7175:          instance(ADC)/None,   Class instance of AD7175, if not using this parameter,
                                               will create emulator
        range_ctrl_pin:  instance(GPIO),       This can be Pin or xilinx gpio, used to control range.
        meter_ctrl_pin:  instance(GPIO),       This can be Pin or xilinx gpio, used to control measure channel
        ipcore:          instance(MIXDAQT1SGR)/string,  MIXDAQT1SGR ipcore driver instance or device name string,
                                                        if given, user should not use ad7175.


    Examples:
        Example for using no-aggregate IP:
            # normal init
            ad7175 = MIXAd7175SG('/dev/MIX_AD717X_0', 5000)
            i2c = I2C('/dev/i2c-1')
            gpio = GPIO(i2c)
            range_ctrl_pin = Pin(gpio, 1)
            meter_ctrl_pin = Pin(gpio, 3)
            wolverine = Wolverine(i2c, ad7175, range_ctrl_pin=range_ctrl_pin, meter_ctrl_pin=meter_ctrl_pin)

            # using ipcore device name string
            i2c = I2C('/dev/i2c-1')
            gpio = GPIO(i2c)
            range_ctrl_pin = Pin(gpio, 1)
            meter_ctrl_pin = Pin(gpio, 3)
            wolverine = Wolverine(i2c, '/dev/MIX_AD717X_0', range_ctrl_pin=range_ctrl_pin,
                                  meter_ctrl_pin=meter_ctrl_pin)

        Example for using aggregate IP:
            # normal init
            i2c = I2C('/dev/i2c-1')
            daqt1 = MIXDAQT1SGR('/dev/MIX_DAQT1', ad717x_chip='AD7175', ad717x_mvref=5000, use_spi=False, use_gpio=True)
            wolverine = Wolverine(i2c, ipcore=daqt1)

            # using ipcore device name
            i2c = I2C('/dev/i2c-1')
            wolverine = Wolverine(i2c, ipcore='/dev/MIX_DAQT1')

        Example for measuring voltage/current:
            wolverine.disable_continuous_sampling()
            wolverine.set_measure_path('5V')
            result = wolverine.read_measure_value()
            print("voltage={}, unit is mV".format(result))
            result = wolverine.read_measure_list(count=5)
            print("voltage_list={}, unit is mV".format(result))

            wolverine.set_measure_path('2mA')
            result = wolverine.read_measure_value()
            print("voltage={}, unit is mA".format(result))
            result = wolverine.read_measure_list(count=5)
            print("voltage_list={}, unit is mA".format(result))

        Example for continuous measuring:
            wolverine.enable_continuous_sampling('5V')
            result = wolverine.read_continuous_sampling_statistics(256)
            print("5V Result: average={}, max={}, min={}, rms={}".format(result['avg_v1'],
            result['max_v1'], result['min_v1'], result['rms_v1']))

            wolverine.enable_continuous_sampling('2mA')
            result = wolverine.read_continuous_sampling_statistics(256)
            print("2mA Result: average={}, max={}, min={}, rms={}".format(result['avg_i'],
            result['max_i'], result['min_i'], result['rms_i']))

    '''
    # launcher will use this to match driver compatible string and load driver if matched.
    compatible = ["GQQ-LTJW-5-030"]

    rpc_public_api = [
        'set_sinc', 'get_sampling_rate', 'set_measure_path', 'get_measure_path',
        'read_measure_value', 'read_measure_list',
        'enable_continuous_sampling',
        'disable_continuous_sampling',
        'read_continuous_sampling_statistics', 'datalogger_start',
        'datalogger_end'
    ] + SGModuleDriver.rpc_public_api

    def __init__(self, i2c, ad7175=None, range_ctrl_pin=None,
                 meter_ctrl_pin=None, ipcore=None):

        if ad7175 and range_ctrl_pin and meter_ctrl_pin and not ipcore:
            if isinstance(ad7175, basestring):
                axi4 = AXI4LiteBus(ad7175, WolverineDef.PLAD7175_REG_SIZE)
                self.ad7175 = MIXAd7175SG(axi4, mvref=WolverineDef.MVREF, code_polar=WolverineDef.POLAR,
                                          clock=WolverineDef.CLOCK)
            else:
                self.ad7175 = ad7175
            self.range_ctrl_pin = range_ctrl_pin
            self.meter_ctrl_pin = meter_ctrl_pin
        elif not ad7175 and not range_ctrl_pin and not meter_ctrl_pin and ipcore:
            if isinstance(ipcore, basestring):
                axi4_bus = AXI4LiteBus(ipcore, WolverineDef.MIX_DAQT1_REG_SIZE)
                self.ipcore = MIXDAQT1SGR(axi4_bus, 'AD7175', ad717x_mvref=WolverineDef.MVREF,
                                          code_polar=WolverineDef.POLAR, clock=WolverineDef.CLOCK, use_gpio=True)
            else:
                self.ipcore = ipcore
            self.ad7175 = self.ipcore.ad717x
            gpio = self.ipcore.gpio
            self.range_ctrl_pin = Pin(gpio, WolverineDef.RANGE_SEL_BIT)
            self.meter_ctrl_pin = Pin(gpio, WolverineDef.METER_SEL_BIT)
            self.tag_pins = [
                Pin(gpio, WolverineDef.TAG_BASE_PIN + x,
                    WolverineDef.GPIO_OUTPUT_DIR) for x in range(4)
            ]
        elif not ad7175 and range_ctrl_pin and meter_ctrl_pin and ipcore:
            if isinstance(ipcore, basestring):
                axi4_bus = AXI4LiteBus(ipcore, WolverineDef.MIX_DAQT1_REG_SIZE)
                self.ipcore = MIXDAQT1SGR(axi4_bus, 'AD7175', ad717x_mvref=WolverineDef.MVREF,
                                          code_polar=WolverineDef.POLAR, clock=WolverineDef.CLOCK, use_gpio=True)
            else:
                self.ipcore = ipcore
            self.ad7175 = self.ipcore.ad717x
            self.range_ctrl_pin = range_ctrl_pin
            self.meter_ctrl_pin = meter_ctrl_pin
            gpio = self.ipcore.gpio
            self.tag_pins = [
                Pin(gpio, WolverineDef.TAG_BASE_PIN + x,
                    WolverineDef.GPIO_OUTPUT_DIR) for x in range(4)
            ]
        else:
            raise WolverineException("Invalid parameter, please check")

        eeprom = CAT24C32(WolverineDef.EEPROM_DEV_ADDR, i2c)
        nct75 = NCT75(WolverineDef.NCT75_DEV_ADDR, i2c)
        super(Wolverine, self).__init__(eeprom, nct75, range_table=wolverine_range_table)

        self.channel_path = {'range_sel_bit': self.range_ctrl_pin, 'meter_sel_bit': self.meter_ctrl_pin}
        self.measure_path = dict()
        self.measure_path['range'] = WolverineDef.VOLT_5V

    def post_power_on_init(self, timeout=WolverineDef.TIME_OUT):
        '''
        Init wolverine module to a know harware state.

        This function will set io direction to output and set default range to '5V'.

        Args:
            timeout:      float, unit Second, execute timeout

        '''
        self.reset(timeout)
        self.load_calibration()
        return "done"

    def reset(self, timeout=WolverineDef.TIME_OUT):
        '''
        Reset the instrument module to a know hardware state.

        Args:
            timeout:      float, unit Second, execute timeout.

        '''
        self.range_ctrl_pin.set_dir('output')
        self.meter_ctrl_pin.set_dir('output')
        self.set_measure_path(WolverineDef.VOLT_5V)
        self.ad7175.channel_init()
        self.set_sampling_rate(WolverineDef.DEFAULT_SAMPLE_RATE)

    def get_driver_version(self):
        '''
        Get wolverine driver version.

        Returns:
            string, current driver version.

        '''
        return __version__

    def write_module_calibration(self, channel, calibration_vectors):
        '''
        Calculate module calibration and write to eeprom.

        Xavier timestamp must be synced with host first before call this function.
        All channels must be calibrated in one day.

        Args:
            channel:        string, ['5V', '2mA', '100uA'], voltage or current channel
            calibration_vectors: list, it contains value pairs of module reading and benchmark
                                 value got from external equipemnets. [[module_raw1,benchmark1],
                                 [module_raw1,benchmark2],
                                 ...
                                 [module_rawN,benchmarkN]]

        Returns:
            string, 'done', execute successful.

        '''
        assert channel in ['5V', '100uA', '2mA']
        return super(Wolverine, self).write_module_calibration(channel, calibration_vectors)

    def set_sinc(self, channel, sinc):
        '''
        wolverineii set digtal filter.

        Args:
            channel:    string, ['100uA', '2mA', '5V'], set range for different channel.
            sinc:       string, ["sinc5_sinc1", "sinc3"]

        Example:
            wolverineii.set_sinc("100uA", "sinc5_sinc1")

        '''
        assert channel in WolverineDef.CHANNEL_CONFIG.keys()
        assert sinc in WolverineDef.ADC_SINC_SEL

        self.ad7175.set_sinc(WolverineDef.CHANNEL_CONFIG[channel]['adc_channel'], sinc)

    def set_sampling_rate(self, sampling_rate):
        '''
        wolverine set sampling rate.

        Note that sampling rate is not discontinuous, all support sampling
        rate can be found in ad7175 datasheet.

        Args:
            sampling_rate:      float, [5~250000], adc measure sampling rate, which is not continuous,
                                                        please refer to ad7175 datasheet.

        Returns:
            string, "done", api execution successful.

        '''
        assert 5 <= sampling_rate <= 250000

        channel = self.measure_path['range']
        if sampling_rate != self.get_sampling_rate(channel):
            self.ad7175.set_sampling_rate(WolverineDef.CHANNEL_CONFIG[channel]['adc_channel'], sampling_rate)

    def get_sampling_rate(self, channel=WolverineDef.VOLT_5V):
        '''
        wolverine Read the sampling rate setting

        Args:
            channel:            string, ['5V', '2mA', '100uA'], get sampling rate for different channel.

        Returns:
            int, value, current module sampling rate in SPS.

        '''
        assert channel in WolverineDef.CHANNEL_CONFIG.keys()

        return self.ad7175.get_sampling_rate(WolverineDef.CHANNEL_CONFIG[channel]['adc_channel'])

    def set_measure_path(self, channel=WolverineDef.VOLT_5V):
        '''
        wolverine set measure path.

        Args:
            channel:       string, ['100uA', '2mA', '5V'], set range for different channel

        Returns:
            string, "done", api execution successful.

        '''
        assert channel in WolverineDef.CHANNEL_CONFIG.keys()

        if channel != self.get_measure_path():
            path_bits = WolverineDef.CHANNEL_CONFIG[channel]['path']
            for bits in path_bits:
                self.channel_path[bits[0]].set_level(bits[1])

            time.sleep(WolverineDef.RELAY_DELAY_S)

        self.measure_path.clear()
        self.measure_path[WolverineDef.SELECT_RANGE_KEY] = channel

        return "done"

    def get_measure_path(self):
        '''
        wolverine get measure path.

        Returns:
            dict, current channel and range.

        '''
        return self.measure_path

    def read_measure_value(self, sample_rate=WolverineDef.DEFAULT_SAMPLE_RATE,
                           count=WolverineDef.DEFAULT_COUNT):
        '''
        Read current average value. The returned value is calibrated if calibration mode is `cal`

        Args:
            sample_rate (int, optional): set sampling rate of data acquisition, in SPS. Default 1000
            count (int, optional): samples count taken for averaging. Default 1

        Returns:
            Int:   measured value defined by set_measure_path()

                   Voltage Channel always in mV

                   Current Channel always in mA
        '''
        assert isinstance(sample_rate, int)
        assert isinstance(count, int) and count >= 1

        measure_path = self.get_measure_path()
        adc_channel = WolverineDef.CHANNEL_CONFIG[measure_path['range']]['adc_channel']

        self.set_sampling_rate(sample_rate)

        cal_item = WolverineDef.CURRENT_CHANNEL + "_" + measure_path['range']
        if measure_path['range'] == WolverineDef.VOLT_5V:
            gain = WolverineDef.VOLTAGE_5V_GAIN
            cal_item = WolverineDef.VOLTAGE_CHANNEL + "_" + measure_path['range']
        elif measure_path['range'] == WolverineDef.CURR_100UA:
            gain = WolverineDef.CURRENT_100UA_GAIN
            sample_res = WolverineDef.CURRENT_100UA_SAMPLE_RES
        else:
            gain = WolverineDef.CURRENT_2MA_GAIN
            sample_res = WolverineDef.CURRENT_2MA_SAMPLE_RES

        target_data = list()
        for x in range(count):
            voltage = self.ad7175.read_volt(adc_channel)
            adc_value = (voltage / gain) if \
                measure_path['range'] == WolverineDef.VOLT_5V else \
                voltage / gain / sample_res
            adc_value = self.calibrate(cal_item, adc_value)
            target_data.append(adc_value)

        target_value = sum(target_data) / count

        return target_value

    def read_measure_list(self, sample_rate=WolverineDef.DEFAULT_SAMPLE_RATE,
                          count=WolverineDef.DEFAULT_COUNT):
        '''
        For example if count is 5, the return list can be: [3711, 3712, 3709, 3703, 3702].

        The returned value is calibrated if calibration mode is `cal`

        Args:
            sample_rate (int, optional): set sampling rate of data acquisition, in SPS. Default 1000
            count (int, optional): samples count taken for averaging. Default 1

        Returns:
            List:   measured value defined by set_measure_path()
                    Voltage Channel always in mV
                    Current Channel always in mA
        '''
        assert isinstance(sample_rate, int)
        assert isinstance(count, int) and count >= 1

        measure_path = self.get_measure_path()
        adc_channel = WolverineDef.CHANNEL_CONFIG[measure_path['range']]['adc_channel']

        self.set_sampling_rate(sample_rate)

        cal_item = WolverineDef.CURRENT_CHANNEL + "_" + measure_path['range']
        if measure_path['range'] == WolverineDef.VOLT_5V:
            gain = WolverineDef.VOLTAGE_5V_GAIN
            cal_item = WolverineDef.VOLTAGE_CHANNEL + "_" + measure_path['range']
        elif measure_path['range'] == WolverineDef.CURR_100UA:
            gain = WolverineDef.CURRENT_100UA_GAIN
            sample_res = WolverineDef.CURRENT_100UA_SAMPLE_RES
        else:
            gain = WolverineDef.CURRENT_2MA_GAIN
            sample_res = WolverineDef.CURRENT_2MA_SAMPLE_RES

        target_data = list()
        for x in range(count):
            voltage = self.ad7175.read_volt(adc_channel)
            adc_value = (voltage / gain) if \
                measure_path['range'] == WolverineDef.VOLT_5V else \
                (voltage / gain / sample_res)
            adc_value = self.calibrate(cal_item, adc_value)
            target_data.append(adc_value)

        return target_data

    def enable_continuous_sampling(self, channel=WolverineDef.VOLT_5V,
                                   sample_rate=WolverineDef.DEFAULT_SAMPLE_RATE,
                                   down_sample=WolverineDef.DEFAULT_DOWN_SAMPLE,
                                   selection=WolverineDef.DEFAULT_SELECTION):
        '''
        This function enables continuous sampling and data throughput upload to upper stream.
        Down sampling is supported.
        For example: when down_sample =5, selection=max, select the maximal value from every 5
            samples, so the actual data rate is reduced by 5.
        The output data inflow is calibrated if calibration mode is `cal`
        During continuous sampling, the setting functions, like set_calibration_mode(),
            set_measure_path(), cannot be called.

        Args:
            channel (string):   '5V':       voltage channel #1
                                '100uA':    100uA current channel
                                '2mA':      2mA current channel
                                Default:    5V
            sample_rate (int):  set sampling rate of data acquisition, in SPS. Default 1000
            down_sample (int):  down sample rate for decimation.
            selection (string): 'max'|'min'. This parameter takes effect as long as down_sample is
                                    higher than 1. Default 'max'

        Returns:
            Str: 'done'
        '''
        assert channel in WolverineDef.CHANNEL_CONFIG.keys()

        adc_channel = WolverineDef.CHANNEL_CONFIG[channel]['adc_channel']
        self.ad7175.disable_continuous_sampling(adc_channel)
        time.sleep(WolverineDef.SWITCH_DELAY)
        self.ad7175.enable_continuous_sampling(adc_channel, sample_rate,
                                               down_sample, selection)

        self.measure_path[WolverineDef.SELECT_RANGE_KEY] = channel

        return "done"

    def disable_continuous_sampling(self):
        '''
        This function disables continuous sampling and data throughput upload to upper stream.
        This function can only be called in continuous mode, a.k.a, after enable_continuous_sampling()
            function is called.

        Returns:
            Str: 'done'
        '''

        measure_path = self.get_measure_path()
        adc_channel = WolverineDef.CHANNEL_CONFIG[measure_path['range']]['adc_channel']
        self.ad7175.disable_continuous_sampling(adc_channel)

        return "done"

    def read_continuous_sampling_statistics(self,
                                            count=WolverineDef.DEFAULT_COUNT):
        '''
        This function takes a number of samples to calculate RMS/average/max/min value of the set of
            sampled value.This function can only be called in continuous mode, a.k.a, after
            enable_continuous_sampling() function is called. Return 0 for the channels that are not enabled.

        The returned value is calibrated if calibration mode is `cal`

        Args:
            count (int): samples count taken for calculation. Default 1

        Returns:
            Dict: {
                (rms_v1, <RMS in mVrms>),
                (avg_v1, <average in mVrms>),
                (max_v1, <maximal in mV>),
                (min_v1, <minimal in mV>),
                (rms_i, <RMS in mArms>),
                (avg_i, <average in mArms>),
                (max_i, <maximal in mA>),
                (min_i, <minimal in mA>)
            }
            for voltage voltage channel #1 and #2.
        '''

        assert isinstance(count, int) and count >= 1

        measure_path = self.get_measure_path()
        adc_channel = WolverineDef.CHANNEL_CONFIG[measure_path['range']]['adc_channel']

        channel_data = list()
        try:
            channel_data = self.ad7175.get_continuous_sampling_voltage(
                adc_channel, count)
        except Exception:
            raise

        min_data = min(channel_data)
        max_data = max(channel_data)
        sum_Data = sum(channel_data)
        avg_data = sum_Data / len(channel_data)
        suqare_sum_data = sum([x**2 for x in channel_data])
        rms_data = math.sqrt(suqare_sum_data / len(channel_data))

        result = dict()
        cal_item = WolverineDef.CURRENT_CHANNEL + "_" + measure_path['range']
        suffix = WolverineDef.CHANNEL_CONFIG[measure_path['range']]['suffix']
        if measure_path['range'] == WolverineDef.VOLT_5V:
            gain = WolverineDef.VOLTAGE_5V_GAIN
            rms = rms_data / gain
            voltage = avg_data / gain
            max_voltage = max_data / gain
            min_voltage = min_data / gain
            cal_item = WolverineDef.VOLTAGE_CHANNEL + "_" + measure_path['range']
            voltage = self.calibrate(cal_item, voltage)

            result['rms_' + suffix] = (rms, 'mVrms')
            result['avg_' + suffix] = (voltage, 'mV')
            result['max_' + suffix] = (max_voltage, 'mV')
            result['min_' + suffix] = (min_voltage, 'mV')
            return result
        elif measure_path['range'] == WolverineDef.CURR_100UA:
            gain = WolverineDef.CURRENT_100UA_GAIN
            sample_res = WolverineDef.CURRENT_100UA_SAMPLE_RES
        else:
            gain = WolverineDef.CURRENT_2MA_GAIN
            sample_res = WolverineDef.CURRENT_2MA_SAMPLE_RES

        rms = (rms_data / gain) / sample_res
        current = (avg_data / gain) / sample_res
        max_current = (max_data / gain) / sample_res
        min_current = (min_data / gain) / sample_res
        current = self.calibrate(cal_item, current)

        result['rms_' + suffix] = (rms, 'mArms')
        result['avg_' + suffix] = (current, 'mA')
        result['max_' + suffix] = (max_current, 'mA')
        result['min_' + suffix] = (min_current, 'mA')
        return result

    def datalogger_start(self, tag=0):
        '''
        Start labeling the samples for on a period of time for calculation.
        This function can only be called in continuous mode, a.k.a, after
        enable_continuous_sampling() function is called.

        Lable shall be on both channels if they are both enabled.

        Args:
            tag (int,  [0, 0x0f]): the value is low 4 bits are valid, from 0x00 to 0x0f.

        Returns:
            Str: 'done'
        '''

        assert tag & 0x0f == tag
        for i in range(4):
            self.tag_pins[i].set_level((tag >> i) & 0x1)

        return "done"

    def datalogger_end(self):
        '''
        Stop labeling the samples. This function can only be called after
            enable_continuous_sampling() and datalogger_start() are called.

        Returns:
            Str: 'done'
        '''

        for i in range(4):
            self.tag_pins[i].set_level(0)

        return "done"
Esempio n. 3
0
class Wolverine(MIXBoard):
    '''
    DMM001 is a compact version of the digital multimeter which internal ADC resolution is 24 bit.

    compatible = ["GQQ-DMM001003-000"]

    It can be used as
    high performance DMM to measure DC voltage and small signal DC current. In this class, adc channel 0 is current
    channel, adc channel 1 is voltage channel. DMM001 support signal measurement and continuous measurement. Note that
    if range_ctrl0 and range_ctrl1 not given, internal submodule GPIO device of MIX_DAQT1 will be used to control range.
    Note that calibration default is enabled. This class is legacy driver for normal boot.

    Args:
        i2c:             instance(I2C)/None, which is used to control nct75 and cat24c32. If not given,
                                            emulator will be created.
        ad7175:          instance(ADC)/None, Class instance of AD7175, if not using this parameter, will create emulator
        range_sel:       instance(GPIO),       This can be Pin or xilinx gpio, used to control range.
        meter_sel:       instance(GPIO),       This can be Pin or xilinx gpio, used to control measure channel
        ipcore:          instance(MIXDAQT1SGR)/string,  MIXDAQT1SGR ipcore driver instance or device name string,
                                              if given, user should not use ad7175.


    Examples:
        Example for using no-aggregate IP:
            # normal init
            ad7175 = MIXAd7175SG('/dev/MIX_AD717X_0', 5000)
            i2c = I2C('/dev/i2c-1')
            gpio = GPIO(i2c)
            range_sel = Pin(gpio, 1)
            meter_sel = Pin(gpio, 3)
            wolverine = Wolverine(i2c, ad7175, range_sel=range_sel, meter_sel=meter_sel)

            # using ipcore device name string
            i2c = I2C('/dev/i2c-1')
            gpio = GPIO(i2c)
            range_sel = Pin(gpio, 1)
            meter_sel = Pin(gpio, 3)
            wolverine = Wolverine(i2c, '/dev/MIX_AD717X_0', range_sel=range_sel, meter_sel=meter_sel)


        Example for using aggregate IP:
            # normal init
            i2c = I2C('/dev/i2c-1')
            daqt1 = MIXDAQT1SGR('/dev/MIX_DAQT1', ad717x_chip='AD7175', ad717x_mvref=5000, use_spi=False, use_gpio=True)
            wolverine = Wolverine(i2c, ipcore=daqt1)

            # using ipcore device name
            i2c = I2C('/dev/i2c-1')
            wolverine = Wolverine(i2c, ipcore='/dev/MIX_DAQT1')

        Example for measuring voltage:
            result = wolverine.voltage_measure(5)
            print("voltage={}, unit={}".format(result[0], result[1]))

            result = wolverine.multi_points_voltage_measure(3, 5)
            print("average={}, max={}, min={}, rms={}".format(result['average'], result['max'],
            result['min'], result['rms']))

        Example for measuring current:
            result = wolverine.current_measure('2mA', 5)
            print("current={}, unit={}".format(result[0], result[1]))

            result = wolverine.multi_points_current_measure(3, '2mA', 5)
            print("average={}, max={}, min={}, rms={}".format(result['average'], result['max'],
            result['min'], result['rms']))

    '''
    # launcher will use this to match driver compatible string and load driver if matched.
    compatible = ["GQQ-DMM001003-000"]

    rpc_public_api = [
        'module_init', 'get_sampling_rate', 'set_measure_path',
        'get_measure_path', 'voltage_measure', 'multi_points_voltage_measure',
        'current_measure', 'multi_points_current_measure'
    ] + MIXBoard.rpc_public_api

    def __init__(self,
                 i2c,
                 ad7175=None,
                 range_sel=None,
                 meter_sel=None,
                 ipcore=None):

        if i2c and ad7175 and range_sel and meter_sel and not ipcore:
            if isinstance(ad7175, basestring):
                axi4 = AXI4LiteBus(ad7175, WolverineDef.PLAD7175_REG_SIZE)
                self.ad7175 = MIXAd7175SG(axi4,
                                          mvref=WolverineDef.MVREF,
                                          code_polar=WolverineDef.POLAR,
                                          clock=WolverineDef.CLOCK)
            else:
                self.ad7175 = ad7175
            self.range_sel = range_sel
            self.meter_sel = meter_sel
        elif i2c and not ad7175 and not range_sel and not meter_sel and ipcore:
            if isinstance(ipcore, basestring):
                axi4_bus = AXI4LiteBus(ipcore, WolverineDef.MIX_DAQT1_REG_SIZE)
                self.ipcore = MIXDAQT1SGR(axi4_bus,
                                          'AD7175',
                                          ad717x_mvref=WolverineDef.MVREF,
                                          code_polar=WolverineDef.POLAR,
                                          clock=WolverineDef.CLOCK,
                                          use_gpio=True)
            else:
                self.ipcore = ipcore
            self.ad7175 = self.ipcore.ad717x
            gpio = self.ipcore.gpio
            self.range_sel = Pin(gpio, WolverineDef.RANGE_SEL_BIT)
            self.meter_sel = Pin(gpio, WolverineDef.METER_SEL_BIT)
        elif i2c and not ad7175 and range_sel and meter_sel and ipcore:
            if isinstance(ipcore, basestring):
                axi4_bus = AXI4LiteBus(ipcore, WolverineDef.REG_SIZE)
                self.ipcore = MIXDAQT1SGR(axi4_bus,
                                          'AD7175',
                                          ad717x_mvref=WolverineDef.MVREF,
                                          code_polar=WolverineDef.POLAR,
                                          clock=WolverineDef.CLOCK,
                                          use_gpio=False)
            else:
                self.ipcore = ipcore
            self.ad7175 = self.ipcore.ad717x
            self.range_sel = range_sel
            self.meter_sel = meter_sel
        elif not i2c and not ad7175 and not range_sel and not meter_sel and not ipcore:
            self.ad7175 = MIXAd7175SGEmulator("ad7175_emulator",
                                              WolverineDef.EMULATOR_REG_SIZE)
            self.range_sel = Pin(None, WolverineDef.RANGE_SEL_BIT)
            self.meter_sel = Pin(None, WolverineDef.METER_SEL_BIT)
        else:
            raise WolverineException("Invalid parameter, please check")
        if i2c:
            eeprom = CAT24C32(WolverineDef.EEPROM_DEV_ADDR, i2c)
            nct75 = NCT75(WolverineDef.NCT75_DEV_ADDR, i2c)
            super(Wolverine,
                  self).__init__(eeprom,
                                 nct75,
                                 cal_table=wolverine_calibration_info,
                                 range_table=wolverine_range_table)
        else:
            super(Wolverine,
                  self).__init__(None,
                                 None,
                                 cal_table=wolverine_calibration_info,
                                 range_table=wolverine_range_table)

        self.channel_path = {
            'range_sel_bit': self.range_sel,
            'meter_sel_bit': self.meter_sel
        }
        self.measure_path = {"channel": None, "range": None}

    def _check_channel(self, channel):
        '''
        Check the channel if it is valid.

        Args:
            channel:  string, ['CURR', 'VOLT'], the channel to check.

        Returns:
            string, ['CURR', 'VOLT'], the channel in specific format.

        Raise:
            WolverineException:  If channel is invalid, exception will be raised.

        '''
        for ch in WolverineDef.CHANNEL_CONFIG:
            if channel.lower() == ch.lower():
                return ch
        raise WolverineException("channel {} is invalid".format(channel))

    def _check_scope(self, channel, scope):
        '''
        Check valid of the specific scope.

        Args:
            channel:     string, the channel to change scope.
            scope:       string, the scope string to be checked.

        Returns:
            string, str, the scope in specific format.

        Raise:
            WolverineException:  If the scope is invalid, exception will be raised.

        '''
        range_config = WolverineDef.CHANNEL_CONFIG[channel]['range']
        for rng in range_config:
            if rng.lower() == scope.lower():
                return rng
        raise WolverineException(
            'scope {} in channel {} is not invalid'.format(channel, scope))

    def module_init(self):
        '''
        Init wolverine module. This function will set io direction to output and set default range to '5V'

        Returns:
            string, "done", api execution successful.

        '''
        self.range_sel.set_dir('output')
        self.meter_sel.set_dir('output')
        self.set_measure_path(WolverineDef.VOLT_5V)
        self.ad7175.channel_init()
        self.set_sampling_rate(WolverineDef.VOLTAGE_CHANNEL,
                               WolverineDef.DEFAULT_SAMPLE_RATE)
        self.set_sampling_rate(WolverineDef.CURRENT_CHANNEL,
                               WolverineDef.DEFAULT_SAMPLE_RATE)
        self.load_calibration()
        return "done"

    def set_sampling_rate(self, channel, sampling_rate):
        '''
        Wolverine set sampling rate.

        Note that sampling rate is not discontinuous, all support sampling
        rate can be found in ad7175 datasheet.

        Args:
            channel:            string, ['VOLT', 'CURR'], the channel to change sampling rate.
            sampling_rate:      float, [5~250000], adc measure sampling rate, which is not continuous,
                                                        please refer to ad7175 datasheet.

        Returns:
            string, "done", api execution successful.

        Examples:
            wolverine.set_sampling_rate('VOLT', 10000)

        '''
        assert 5 <= sampling_rate <= 250000
        channel = self._check_channel(channel)

        if sampling_rate != self.get_sampling_rate(channel):
            self.ad7175.set_sampling_rate(
                WolverineDef.CHANNEL_CONFIG[channel]['adc_channel'],
                sampling_rate)

    def get_sampling_rate(self, channel):
        '''
        Wolverine get sampling rate of adc

        Args:
            channel:            string, ['VOLT', 'CURR'], the channel to get sampling rate.

        Returns:
            int, value, current module sampling rate.

        Examples:
            sampling_rate = wolverine.set_sampling_rate('VOLT')
            print(sampling_rate)

        '''
        channel = self._check_channel(channel)

        return self.ad7175.get_sampling_rate(
            WolverineDef.CHANNEL_CONFIG[channel]['adc_channel'])

    def _get_channel_from_scope(self, scope):
        '''
        Get channel from scope string.

        Args:
            scope:     string, ['5V', '2mA', '100uA'], measure range string.

        Examples:
            string, ['VOLT', 'CURR', wolverine measure channel.

        '''
        if scope.lower() == WolverineDef.VOLT_5V.lower():
            return WolverineDef.VOLTAGE_CHANNEL
        elif scope.lower() in [
                WolverineDef.CURR_2MA.lower(),
                WolverineDef.CURR_100UA.lower()
        ]:
            return WolverineDef.CURRENT_CHANNEL
        else:
            raise WolverineException("scope {} is invalid.".format(scope))

    def set_measure_path(self, scope):
        '''
        Wolverine set measure path.

        Args:
            scope:       string, ['100uA', '2mA', '5V''], set range for different channel

        Returns:
            string, "done", api execution successful.

        Examples:
            wolverine.setmeasure_path('5V')

        '''
        channel = self._get_channel_from_scope(scope)
        scope = self._check_scope(channel, scope)

        path_bits = WolverineDef.CHANNEL_CONFIG[channel]['range'][scope][
            'path']

        if self.measure_path["channel"] != channel or \
                self.measure_path["range"] != scope:
            for bits in path_bits:
                self.channel_path[bits[0]].set_level(bits[1])

        self.measure_path["channel"] = channel
        self.measure_path["range"] = scope

    def get_measure_path(self):
        '''
        Wolverine get measure path.

        Returns:
            dict, current channel and range.

        Examples:
            path = wolverine.get_measure_path()
            printf(path)

        '''
        return self.measure_path

    def voltage_measure(self, sampling_rate=WolverineDef.DEFAULT_SAMPLE_RATE):
        '''
        Wolverine measure voltage once

        Args:
            sampling_rate:  float, [5~250000], default 5, not continuous, please refer to ad7175 datasheet.

        Returns:
            list, [value, 'mV'], voltage value and unit.

        '''

        # Slect range: VOLT_5V = '5V'
        self.set_measure_path(WolverineDef.VOLT_5V)

        # Set sampling_rate
        self.set_sampling_rate(WolverineDef.VOLTAGE_CHANNEL, sampling_rate)

        voltage = self.ad7175.read_volt(WolverineDef.CHANNEL_CONFIG[
            self.measure_path['channel']]['adc_channel'])
        gain = WolverineDef.VOLTAGE_5V_GAIN
        voltage /= gain
        cal_item = self.measure_path["channel"] + "_" + self.measure_path[
            "range"]
        voltage = self.calibrate(cal_item, voltage)

        return [voltage, 'mV']

    def multi_points_voltage_measure(self,
                                     count,
                                     sampling_rate=WolverineDef.
                                     DEFAULT_SAMPLE_RATE):
        '''
        Wolverine measure voltage in continuous mode.

        Notice that before call this function,
        continuous voltage measure has been started.

        Args:
            count:          int, [1~512], Get count voltage in continuous mode.
            sampling_rate:  float, [5~250000], default 5, not continuous, please refer to ad7175 datasheet.

        Returns:
            dict, {"rms":[value, 'mVrms'], "average":[value, 'mV'], "max":[value, 'mV'], "min":[value, 'mV']},
                  rms, average, max and min voltage with unit.

        '''

        adc_channel = WolverineDef.CHANNEL_CONFIG[
            self.measure_path['channel']]['adc_channel']

        # Slect range: VOLT_5V = '5V'
        self.set_measure_path(WolverineDef.VOLT_5V)

        self.ad7175.disable_continuous_sampling(adc_channel)
        time.sleep(WolverineDef.SWITCH_DELAY)
        self.ad7175.enable_continuous_sampling(adc_channel, sampling_rate)
        adc_volt = self.ad7175.get_continuous_sampling_voltage(
            adc_channel, count)
        self.ad7175.disable_continuous_sampling(adc_channel)

        min_data = min(adc_volt)
        max_data = max(adc_volt)
        sum_Data = sum(adc_volt)
        avg_data = sum_Data / len(adc_volt)
        suqare_sum_data = sum([x**2 for x in adc_volt])
        rms_data = math.sqrt(suqare_sum_data / len(adc_volt))

        gain = WolverineDef.VOLTAGE_5V_GAIN

        rms = rms_data / gain
        voltage = avg_data / gain
        max_voltage = max_data / gain
        min_voltage = min_data / gain

        cal_item = self.measure_path["channel"] + "_" + self.measure_path[
            "range"]
        voltage = self.calibrate(cal_item, voltage)

        result = dict()
        result['rms'] = (rms, 'mVrms')
        result['average'] = (voltage, 'mV')
        result['max'] = (max_voltage, 'mV')
        result['min'] = (min_voltage, 'mV')

        return result

    def current_measure(self,
                        curr_range=WolverineDef.CURR_2MA,
                        sampling_rate=WolverineDef.DEFAULT_SAMPLE_RATE):
        '''
        Wolverine measure current once

        Args:
            curr_range:     string, ['2mA', '100uA'], default '2mA', measure range string.
            sampling_rate:  float, [5~250000], default 5, not continuous, please refer to ad7175 datasheet.

        Returns:
            list, [value, 'mA'], current value and unit.

        '''
        assert curr_range in [WolverineDef.CURR_100UA, WolverineDef.CURR_2MA]

        # Slect range: CURR_100UA = '100uA', CURR_2MA = '2mA'
        self.set_measure_path(curr_range)

        # Set sampling_rate
        self.set_sampling_rate(WolverineDef.CURRENT_CHANNEL, sampling_rate)

        volt = self.ad7175.read_volt(WolverineDef.CHANNEL_CONFIG[
            self.measure_path['channel']]['adc_channel'])

        sample_res = WolverineDef.CURRENT_100UA_SAMPLE_RES if \
            self.measure_path["range"] == WolverineDef.CURR_100UA else \
            WolverineDef.CURRENT_2MA_SAMPLE_RES
        gain = WolverineDef.CURRENT_100UA_GAIN if \
            self.measure_path["range"] == WolverineDef.CURR_100UA else \
            WolverineDef.CURRENT_2MA_GAIN
        current = (volt / gain) / sample_res
        cal_item = self.measure_path["channel"] + "_" + self.measure_path[
            "range"]
        current = self.calibrate(cal_item, current)

        return [current, 'mA']

    def multi_points_current_measure(
            self,
            count,
            curr_range=WolverineDef.CURR_2MA,
            sampling_rate=WolverineDef.DEFAULT_SAMPLE_RATE):
        '''
        Wolverine measure current in continuous mode.

        Note that before call this function,
        continuous current measure must be started first

        Args:
            count:           int, [1~512], number current value to be get
            curr_range:      string, ['2mA', '100uA'], default '2mA', measure range string.
            sampling_rate:   float, [5~250000], default 5, not continuous, please refer to ad7175 datasheet.

        Returns:
            dict, {"rms":[value, 'mVrms'], "average":[value, 'mA'], "max":[value, 'mA'], "min":[value, 'mA']},
                  rms, average, max and min current with unit.

        '''
        assert curr_range in [WolverineDef.CURR_100UA, WolverineDef.CURR_2MA]

        adc_channel = WolverineDef.CHANNEL_CONFIG[
            self.measure_path['channel']]['adc_channel']

        # Slect range: CURR_100UA = '100uA', CURR_2MA = '2mA'
        self.set_measure_path(curr_range)

        self.ad7175.disable_continuous_sampling(adc_channel)
        time.sleep(WolverineDef.SWITCH_DELAY)

        self.ad7175.enable_continuous_sampling(adc_channel, sampling_rate)

        adc_volt = self.ad7175.get_continuous_sampling_voltage(
            adc_channel, count)
        self.ad7175.disable_continuous_sampling(adc_channel)

        min_data = min(adc_volt)
        max_data = max(adc_volt)
        sum_Data = sum(adc_volt)
        avg_data = sum_Data / len(adc_volt)
        suqare_sum_data = sum([x**2 for x in adc_volt])
        rms_data = math.sqrt(suqare_sum_data / len(adc_volt))

        sample_res = WolverineDef.CURRENT_100UA_SAMPLE_RES if \
            self.measure_path["range"] == WolverineDef.CURR_100UA else \
            WolverineDef.CURRENT_2MA_SAMPLE_RES
        gain = WolverineDef.CURRENT_100UA_GAIN if \
            self.measure_path["range"] == WolverineDef.CURR_100UA else \
            WolverineDef.CURRENT_2MA_GAIN

        rms = (rms_data / gain) / sample_res
        current = (avg_data / gain) / sample_res
        max_current = (max_data / gain) / sample_res
        min_current = (min_data / gain) / sample_res

        cal_item = self.measure_path["channel"] + "_" + self.measure_path[
            "range"]
        current = self.calibrate(cal_item, current)

        result = dict()
        result['rms'] = (rms, 'mArms')
        result['average'] = (current, 'mA')
        result['max'] = (max_current, 'mA')
        result['min'] = (min_current, 'mA')

        return result

    def legacy_write_calibration_cell(self, unit_index, gain, offset,
                                      threshold):
        '''
        MIXBoard calibration data write

        Args:
            unit_index:   int,    calibration unit index.
            gain:         float,  calibration gain.
            offset:       float,  calibration offset.
            threshold:    float,  if value < threshold, use this calibration unit data.

        Returns:
            string, "done", api execution successful.

        Examples:
            board.write_calibration_cel(0, 1.1, 0.1, 100)

        Raise:
            BoardArgCheckError: unit_index data type is not int type or unit_index < 0.
            calibration unit format:
                Meaning:    Gain,     Offset,   threshold value, Use flag
                Mem size:   4Bytes,   4Bytes,   4Bytes,            Byte
                Data type:  float,    float,    float,            uint8_t
                Formula:    Y = Gain * X  + Offset

        '''
        if not isinstance(unit_index, int) or unit_index < 0:
            raise BoardArgCheckError(
                "calibration unit memory unit_index data type is not int type or unit_index < 0"
            )

        use_flag = self.calibration_info["use_flag"]
        data = (gain, offset, use_flag)
        s = struct.Struct(WolverineDef.WRITE_CAL_DATA_PACK_FORMAT)
        pack_data = s.pack(*data)

        s = struct.Struct(WolverineDef.WRITE_CAL_DATA_UNPACK_FORMAT)
        data = s.unpack(pack_data)
        address = self.calibration_info[
            "unit_start_addr"] + WolverineDef.CAL_DATA_LEN * unit_index
        self.write_eeprom(address, data)
        return "done"

    def legacy_read_calibration_cell(self, unit_index):
        '''
        MIXBoard read calibration data

        Args:
            unit_index: int, calibration unit index.

        Returns:
            dict, {"gain": value, "offset":value, "threshold": value, "is_use": value}.

        Examples:
            data = board.read_calibration_cel(0)
            print(data)

        Raise:
            BoardArgCheckError: unit_index data type is not int type or unit_index < 0.
            calibration unit format:
                Meaning:    Gain,     Offset,   threshold value, Use flag
                Mem size:   4Bytes,   4Bytes,   4Bytes,            Byte
                Data type:  float,    float,    float,            uint8_t
                Formula:    Y = Gain * X  + Offset

        '''
        if not isinstance(unit_index, int) or unit_index < 0:
            raise BoardArgCheckError(
                "calibration unit memory unit_index data type is not int type or unit_index < 0"
            )

        address = self.calibration_info[
            "unit_start_addr"] + WolverineDef.CAL_DATA_LEN * unit_index
        data = self.read_eeprom(address, WolverineDef.READ_CAL_BYTE)

        s = struct.Struct(WolverineDef.READ_CAL_DATA_PACK_FORMAT)
        pack_data = s.pack(*data)

        s = struct.Struct(WolverineDef.READ_CAL_DATA_UNPACK_FORMAT)
        result = s.unpack(pack_data)

        threshold = 0.0
        for cal_item in wolverine_calibration_info:
            for level in wolverine_calibration_info[cal_item]:
                if unit_index == wolverine_calibration_info[cal_item][level][
                        "unit_index"]:
                    threshold = wolverine_calibration_info[cal_item][level][
                        "limit"][0]

        if self.calibration_info["use_flag"] != result[2]:
            return {"gain": 1.0, "offset": 0.0, "threshold": 0, "is_use": True}
        else:
            return {
                "gain": result[0],
                "offset": result[1],
                "threshold": threshold,
                "is_use": True
            }
Esempio n. 4
0
class Audio005002(Audio005Base):
    '''
    Audio005002 is a high resolution differential input/output digital audio module.

    Args:
        i2c:             instance(I2C), the instance of I2C bus. which will be used to used
                                        to control eeprom, sensor and io expander.
        ipcore:          instance(MIXAudio005SGR), the instance of MIXAudio005SGR, which include
                                                   AD717x, FFT Analyzer, Signal Source, RAM Signal,
                                                   Audio Cache  and gpio function.
                                                   If device name string is passed to the parameter,
                                                   the ipcore can be instanced in the module.
        dma:             instance(MIXDMASG)/None, the instance of MIXDMASG. which will be used to used
        left_ch_id:      int/None, [0~15], Id of the left channel to be configured.
        right_ch_id:     int/None, [0~15], Id of the right channel to be configured.
        dma_mem_size:    int/None, [0~MIXDMASG_MEM_SIZE], default 1M, unit M, MIX_DMA_SG channel size.
                                   1M=1024*1024 Byte and a point is 32 bit, which is 4 Byte,
                                   so 1M=1024*1024/4=262144 points.

    Examples:
        i2c = I2C('/dev/i2c-0')
        audio005002 = Audio005002(i2c, '/dev/MIX_Audio005002_SG_R')
        audio005002.post_power_on_init()
        # measure left channel input
        LEFT_CHANNEL = 1
        audio005002.configure_input_channel(LEFT_CHANNEL, True, False, 48000)
        # start recording
        # wait for 1 s
        # collect samples
        # stop recording
        audio005002.configure_input_channel(LEFT_CHANNEL, False)
        data = audio005002.read(LEFT_CHANNEl, 8192)
        print data
    '''
    rpc_public_api = [
        'configure_input_channel', 'get_input_channel_configuration',
        'configure_output_channel', 'get_output_channel_configuration', 'read',
        'write', 'get_noisefloor'
    ] + Audio005Base.rpc_public_api

    # launcher will use this to match driver compatible string and load driver if matched.
    compatible = ["GQQ-03C6-5-020", "GQQ-03C6-5-02A"]

    def __init__(self,
                 i2c,
                 ipcore,
                 dma=None,
                 left_ch_id=None,
                 right_ch_id=None,
                 dma_mem_size=1):
        super(Audio005002, self).__init__(i2c, ipcore)

        self.ram_signal = self.ipcore.ram_signal
        self.audio_cache = self.ipcore.audio_cache
        self.ram_out_pin = Pin(self.ipcore.gpio, Audio005002Def.RAM_OUT_PIN)

        self.adc_sample_rate = Audio005002Def.DEF_SAMPLING_RATE
        self.dac_sample_rate = Audio005002Def.AUDIO_SAMPLING_RATE
        self.vref = [
            Audio005002Def.ANALYZER_2V_VOLT, Audio005002Def.ANALYZER_2V_VOLT
        ]
        self.input_enable = [False, False]
        self.output_enable = False
        self.model = Audio005002Def.AUDIO005002_CONFIG

        self.dma = dma
        if self.dma is not None:
            self.left_ch_id = left_ch_id
            self.right_ch_id = right_ch_id
            self.dma_mem_size = dma_mem_size
            if self.left_ch_id is None or self.right_ch_id is None or self.dma_mem_size is None:
                raise Audio005002Exception("dma parameter error")

    def read_hardware_config(self):
        '''
        returns a string that represents the config
        '''
        return "".join(
            [chr(c) for c in self.read_nvmem(Audio005002Def.CONFIG_ADDR, 3)])

    def load_calibration(self):
        '''
        Load calibration data. If GQQ is defined in eeprom, this function will load calibration defined.
        '''
        self.model = self.read_hardware_config()
        if self.model == Audio005002Def.AUDIO005002_CONFIG:
            self._range_table = starlordii_range_table
        else:
            # Compatible with 02A model
            self._range_table = audio005002_range_table
        super(Audio005002, self).load_calibration()

    def post_power_on_init(self, timeout=Audio005002Def.TIME_OUT):
        '''
        Init audio005 module to a know harware state.

        This function will reset reset dac/adc and i2s module.

        Args:
            timeout:      float, (>=0), default 1, unit Second, execute timeout.
        '''
        self.reset(timeout)
        if self.dma is not None:
            self.dma.config_channel(self.left_ch_id,
                                    self.dma_mem_size * 0X100000)
            self.dma.config_channel(self.right_ch_id,
                                    self.dma_mem_size * 0X100000)

    def reset(self, timeout=Audio005002Def.TIME_OUT):
        '''
        Reset the instrument module to a know hardware state.

        Args:
            timeout:      float, (>=0), default 1, unit Second, execute timeout.
        '''
        start_time = time.time()
        while True:
            try:
                self.adc_rst_pin.set_dir(Audio005002Def.IO_DIR_OUTPUT)
                self.dac_rst_pin.set_dir(Audio005002Def.IO_DIR_OUTPUT)
                self.i2s_rx_en_pin.set_dir(Audio005002Def.IO_DIR_OUTPUT)
                self.i2s_tx_en_pin.set_dir(Audio005002Def.IO_DIR_OUTPUT)
                self.ram_out_pin.set_dir(Audio005002Def.IO_DIR_OUTPUT)

                # reset ADC
                self.adc_rst_pin.set_level(Audio005002Def.PIN_LEVEL_LOW)
                time.sleep(Audio005002Def.RELAY_DELAY_S)
                self.adc_rst_pin.set_level(Audio005002Def.PIN_LEVEL_HIGH)

                # reset DAC
                self.dac_rst_pin.set_level(Audio005002Def.PIN_LEVEL_LOW)
                time.sleep(Audio005002Def.RELAY_DELAY_S)
                self.dac_rst_pin.set_level(Audio005002Def.PIN_LEVEL_HIGH)

                # reset i2s rx
                self.i2s_rx_en_pin.set_level(Audio005002Def.PIN_LEVEL_LOW)

                # reset i2s tx
                self.i2s_tx_en_pin.set_level(Audio005002Def.PIN_LEVEL_LOW)

                self.ram_out_pin.set_level(Audio005002Def.PIN_LEVEL_LOW)
                # io init
                self.pca9536.set_pin_dir(Audio005002Def.ADC_CH_RIGHT_CTL_BIT,
                                         Audio005002Def.IO_DIR_OUTPUT)
                self.pca9536.set_pin_dir(Audio005002Def.ADC_CH_LEFT_CTL_BIT,
                                         Audio005002Def.IO_DIR_OUTPUT)
                self.pca9536.set_pin_dir(Audio005002Def.ADCM0_CTL_BIT,
                                         Audio005002Def.IO_DIR_OUTPUT)
                self.pca9536.set_pin_dir(Audio005002Def.ADCM1_CTL_BIT,
                                         Audio005002Def.IO_DIR_OUTPUT)

                self.pca9536.set_pin(Audio005002Def.ADC_CH_RIGHT_CTL_BIT,
                                     Audio005002Def.PIN_LEVEL_HIGH)
                self.pca9536.set_pin(Audio005002Def.ADC_CH_LEFT_CTL_BIT,
                                     Audio005002Def.PIN_LEVEL_HIGH)
                self.pca9536.set_pin(Audio005002Def.ADCM0_CTL_BIT,
                                     Audio005002Def.PIN_LEVEL_LOW)
                self.pca9536.set_pin(Audio005002Def.ADCM1_CTL_BIT,
                                     Audio005002Def.PIN_LEVEL_LOW)

                self.model = self.read_hardware_config()
                if self.model == Audio005002Def.AUDIO005002_CONFIG:
                    self._range_table = starlordii_range_table
                else:
                    # Compatible with 02A model
                    self._range_table = audio005002_range_table

                return
            except Exception as e:
                if time.time() - start_time > timeout:
                    raise Audio005002Exception("Timeout: {}".format(e.message))

    def get_driver_version(self):
        '''
        Get audio005 driver version.

        Returns:
            string, current driver version.
        '''
        return __version__

    def measure(self,
                channel,
                scope,
                bandwidth_hz,
                decimation_type=0xFF,
                sampling_rate=Audio005002Def.DEF_SAMPLING_RATE):
        '''
        Measure audio input signal, which captures data using CS5361.

        Args:
            channel:         string, ['left', 'right'], select input signal channel.
            scope:           string, ['2V', '20mV'], AD7175 measurement range.
            bandwidth_hz:    int/string, [42~48000], unit Hz, the signal bandwidth.
                             In theory the bandwidth must smaller than half the sampling rate.
                             eg, if sampling_rate = 192000, so bandwidth_hz  < 96000.
                             The bandwidth must be greater than the frequency of the input signal.
            decimation_type: int, [1~255], default 0xFF, sample data decimation.
                             decimation_type is 1 means not to decimate.
                             The smaller the input frequency, the larger the value should be.
            sampling_rate:   int, [0~192000], default 48000, unit Hz, ADC sampling rate.

        Returns:
            dict, {'vpp': value, 'freq': value, 'thd': value, 'thdn': value, 'rms': value, 'noisefloor': value},
            measurement result.
        '''
        assert channel in Audio005002Def.AUDIO_CHANNEL_LIST
        assert scope in Audio005002Def.LNA_SCOPE

        if self.is_enable_upload is False:
            self.i2s_rx_en_pin.set_level(Audio005002Def.I2S_RX_ENABLE)

        pin = self.i2s_ch_select[Audio005002Def.SELECT_BIT0]
        pin.set_level(Audio005002Def.AUDIO_CHANNEL_LIST[channel][
            Audio005002Def.SELECT_BIT0])
        pin = self.i2s_ch_select[Audio005002Def.SELECT_BIT1]
        pin.set_level(Audio005002Def.AUDIO_CHANNEL_LIST[channel][
            Audio005002Def.SELECT_BIT1])

        if scope == Audio005002Def.LNA_RANGE_2V:
            self.pca9536.set_pin(Audio005002Def.ADC_CH_LIST[channel],
                                 Audio005002Def.SEL_GAIN_1)
            gain = Audio005002Def.AUDIO_ANALYZER_2V_VREF
        else:
            self.pca9536.set_pin(Audio005002Def.ADC_CH_LIST[channel],
                                 Audio005002Def.SEL_GAIN_100)
            gain = Audio005002Def.AUDIO_ANALYZER_20mV_VREF

        index = bisect.bisect(Audio005002Def.SAMPLING_RANGE, sampling_rate)

        pin_level = Audio005002Def.ADCM_CTL_LIST[index][
            Audio005002Def.SELECT_BIT0]
        self.pca9536.set_pin(Audio005002Def.ADCM0_CTL_BIT, pin_level)
        pin_level = Audio005002Def.ADCM_CTL_LIST[index][
            Audio005002Def.SELECT_BIT1]
        self.pca9536.set_pin(Audio005002Def.ADCM1_CTL_BIT, pin_level)

        self.analyzer.disable()
        self.analyzer.enable()
        self.analyzer.analyze_config(sampling_rate, decimation_type,
                                     bandwidth_hz)
        self.analyzer.analyze()

        freq = self.analyzer.get_frequency()

        harmonic_count = int(Audio005002Def.MAX_HARMONIC_WIDTH / freq)
        harmonic_count = 10 if harmonic_count > 10 else harmonic_count
        harmonic_count = 2 if harmonic_count <= 1 else harmonic_count

        self.analyzer.set_harmonic_count(harmonic_count)

        vpp = self.analyzer.get_vpp() * gain
        rms = vpp / Audio005002Def.RMS_TO_VPP_RATIO

        if scope == Audio005002Def.LNA_RANGE_2V:
            # Since the calibration range is within 20000Hz, the actual measurement result may be slightly
            # greater than 20000Hz when the input signal frequency is 20000Hz.
            # Therefore, the calibration frequency is appropriately increased to 20010Hz.
            if self.model == Audio005002Def.AUDIO005002_CONFIG:
                if freq > 20000:
                    index = bisect.bisect(Audio005002Def.CAL_RANGE, freq - 10)
                else:
                    index = bisect.bisect(Audio005002Def.CAL_RANGE, freq)
                range_name = "AUDIO_2V_" + str(
                    Audio005002Def.CAL_RANGE[index]) + "Hz_" + channel
            else:
                # Compatible with 02A model
                range_name = "AUDIO_2V_" + channel
        else:
            range_name = "AUDIO_20mV_" + channel

        rms = self.calibrate(range_name, rms)
        vpp = rms * Audio005002Def.RMS_TO_VPP_RATIO
        thdn_value = self.analyzer.get_thdn()

        result = dict()
        result["vpp"] = (vpp, Audio005002Def.VOLT_UNIT_MV)
        result["freq"] = (freq, Audio005002Def.FREQ_UNIT_HZ)
        result["thd"] = (self.analyzer.get_thd(), Audio005002Def.THD_UNIT_DB)
        result["thdn"] = (thdn_value, Audio005002Def.THDN_UNIT_DB)
        result["rms"] = (rms, Audio005002Def.VOLT_UNIT_RMS)
        result["noisefloor"] = (10**(thdn_value / 20) * rms,
                                Audio005002Def.VOLT_UNIT_RMS)

        if self.is_enable_upload is False:
            self.i2s_rx_en_pin.set_level(Audio005002Def.I2S_RX_DISABLE)

        return result

    def get_noisefloor(self,
                       channel,
                       scope,
                       bandwidth_hz,
                       decimation_type=0xFF,
                       sampling_rate=192000):
        '''
        Measure audio input signal, which captures data using CS5361.

        Args:
            channel:         string, ['left', 'right'], select input signal channel.
            scope:           string, ['2V', '20mV'], AD7175 measurement range.
            bandwidth_hz:    int/string, [42~48000], unit Hz, the signal bandwidth.
                             In theory the bandwidth must smaller than half the sampling rate.
                             eg, if sampling_rate = 192000, so bandwidth_hz  < 96000.
                             The bandwidth must be greater than the frequency of the input signal.
            decimation_type: int, [1~255], default 0xFF, sample data decimation.
                             decimation_type is 1 means not to decimate.
                             The smaller the input frequency, the larger the value should be.
            sampling_rate:   int, [0~192000], default 192000, unit Hz, ADC sampling rate.

        Returns:
            float, value, unit mVrms,  Result of signal's noisefloor.

        '''
        if self.is_enable_upload is False:
            self.i2s_rx_en_pin.set_level(Audio005002Def.I2S_RX_ENABLE)

        pin = self.i2s_ch_select[Audio005002Def.SELECT_BIT0]
        pin.set_level(Audio005002Def.AUDIO_CHANNEL_LIST[channel][
            Audio005002Def.SELECT_BIT0])
        pin = self.i2s_ch_select[Audio005002Def.SELECT_BIT1]
        pin.set_level(Audio005002Def.AUDIO_CHANNEL_LIST[channel][
            Audio005002Def.SELECT_BIT1])

        if scope == Audio005002Def.LNA_RANGE_2V:
            self.pca9536.set_pin(Audio005002Def.ADC_CH_LIST[channel],
                                 Audio005002Def.SEL_GAIN_1)
            gain = Audio005002Def.AUDIO_ANALYZER_2V_VREF
        else:
            self.pca9536.set_pin(Audio005002Def.ADC_CH_LIST[channel],
                                 Audio005002Def.SEL_GAIN_100)
            gain = Audio005002Def.AUDIO_ANALYZER_20mV_VREF

        index = bisect.bisect(Audio005002Def.SAMPLING_RANGE, sampling_rate)

        pin_level = Audio005002Def.ADCM_CTL_LIST[index][
            Audio005002Def.SELECT_BIT0]
        self.pca9536.set_pin(Audio005002Def.ADCM0_CTL_BIT, pin_level)
        pin_level = Audio005002Def.ADCM_CTL_LIST[index][
            Audio005002Def.SELECT_BIT1]
        self.pca9536.set_pin(Audio005002Def.ADCM1_CTL_BIT, pin_level)

        self.analyzer.disable()
        self.analyzer.enable()
        self.analyzer.analyze_config(sampling_rate, decimation_type,
                                     bandwidth_hz)
        self.analyzer.analyze()

        noisefloor = self.analyzer.get_noisefloor() * gain

        if self.is_enable_upload is False:
            self.i2s_rx_en_pin.set_level(Audio005002Def.I2S_RX_DISABLE)

        return noisefloor

    def enable_output(self, freq, vpp):
        '''
        Audio005 CS5361 output audio sine waveform.

        Args:
            freq:       int, [5~50000], unit Hz, output signal's frequency.
            vpp:        float, [0~6504], unit mV, output signal's vpp.

        Returns:
            string, "done", execution successful.
        '''
        assert Audio005002Def.OUTPUT_FREQ_MIN <= freq
        assert freq <= Audio005002Def.OUTPUT_FREQ_MAX
        assert Audio005002Def.OUTPUT_VPP_MIN <= vpp
        assert vpp <= Audio005002Def.OUTPUT_VPP_MAX

        vpp = self.calibrate(Audio005002Def.OUTPUT_CAL_ITEM, vpp)
        vpp = 0 if vpp < 0 else vpp
        # enable I2S tx module
        self.i2s_tx_en_pin.set_level(Audio005002Def.AUDIO_OUTPUT_ENABLE)
        self.ram_out_pin.set_level(Audio005002Def.USE_SIGNAL_SOURCE)

        self.signal_source.close()
        self.signal_source.open()
        # calculate vpp to vpp scale for FPGA
        vpp_scale = vpp * Audio005002Def.VPP_2_SCALE_RATIO
        self.signal_source.set_swg_paramter(Audio005002Def.AUDIO_SAMPLING_RATE,
                                            freq, vpp_scale,
                                            Audio005002Def.OUTPUT_SIGNAL_DUTY)
        self.signal_source.set_signal_type(Audio005002Def.OUTPUT_WAVE)
        self.signal_source.set_signal_time(Audio005002Def.SIGNAL_ALWAYS_OUTPUT)
        self.signal_source.output_signal()

        return "done"

    def configure_input_channel(self,
                                channel,
                                enable,
                                enable_lna=False,
                                sample_rate=Audio005002Def.DEF_SAMPLING_RATE):
        '''
        Configure input channel based on given parameters.

        Args:
            channel:      int, [1, 2], currently defined are 1(left), 2(right).
            enable:       boolean, indicating if given path should be enabled.
            enable_lna:   boolean, true if LNA should be enabled for given input path.
            sample_rate:  int, [48000, 96000, 192000], Sample rate to use for given channel.

        Exception:
            InvalidHardwareChannel() if provided channel is not 1 or 2.
            InvalidSampleRate() if sample_rate is bad.

        Return:
            dict, { "channel" : 1, "sample_rate" : 48000, "enable" : True, "enable_lna" : True,
                    "supported_sample_rates" : [48000, 96000, 192000] }, Actual channel configuration.
        '''
        if channel not in [1, 2]:
            raise InvalidHardwareChannel("channel is not 1 or 2")

        if sample_rate not in [48000, 96000, 192000]:
            raise InvalidSampleRate("sample_rate is bad")

        ADC_CH_LIST = {1: 1, 2: 0}
        CHANNEL_LIST = {1: self.left_ch_id, 2: self.right_ch_id}
        code = channel - 1

        if enable is True:
            self.i2s_rx_en_pin.set_level(Audio005002Def.I2S_RX_ENABLE)

        if enable_lna is True:
            self.pca9536.set_pin(ADC_CH_LIST[channel],
                                 Audio005002Def.SEL_GAIN_100)
            self.vref[code] = Audio005002Def.ANALYZER_20mV_VOLT
        else:
            self.pca9536.set_pin(ADC_CH_LIST[channel],
                                 Audio005002Def.SEL_GAIN_1)
            self.vref[code] = Audio005002Def.ANALYZER_2V_VOLT

        index = bisect.bisect(Audio005002Def.SAMPLING_RANGE, sample_rate)
        pin_level = Audio005002Def.ADCM_CTL_LIST[index][
            Audio005002Def.SELECT_BIT0]
        self.pca9536.set_pin(Audio005002Def.ADCM0_CTL_BIT, pin_level)
        pin_level = Audio005002Def.ADCM_CTL_LIST[index][
            Audio005002Def.SELECT_BIT1]
        self.pca9536.set_pin(Audio005002Def.ADCM1_CTL_BIT, pin_level)
        self.adc_sample_rate = sample_rate

        if enable is True:
            time.sleep(Audio005002Def.PIN_RELAY_DELAY_S)
            self.dma.enable_channel(CHANNEL_LIST[channel])
            self.dma.reset_channel(CHANNEL_LIST[channel])
            self.input_enable[code] = True
        else:
            self.i2s_rx_en_pin.set_level(Audio005002Def.I2S_RX_DISABLE)
            self.dma.disable_channel(CHANNEL_LIST[channel])
            self.input_enable[code] = False

        result = dict()
        result["channel"] = channel
        result["sample_rate"] = self.adc_sample_rate
        result["enable"] = enable
        result["enable_lna"] = enable_lna
        result["supported_sample_rates"] = [48000, 96000, 192000]

        return result

    def get_input_channel_configuration(self, channel):
        '''
        Returns the configuration of the specified channel.

        Args:
            channel: int, [1, 2], the desired channel.

        Exception:
            InvalidHardwareChannel() if provided channel is not 1 or 2.

         Returns:
            dict, { "channel" : 1, "sample_rate" : 48000, "enable" : True, "enable_lna" : True,
                    "supported_sample_rates" : [48000, 96000, 192000] }.
        '''
        if channel not in [1, 2]:
            raise InvalidHardwareChannel("channel is not 1 or 2")
        code = channel - 1

        if self.input_enable[code] is True:
            enable = True
        else:
            enable = False

        if self.vref[code] == Audio005002Def.ANALYZER_20mV_VOLT:
            enable_lna = True
        else:
            enable_lna = False

        result = dict()
        result["channel"] = channel
        result["sample_rate"] = self.adc_sample_rate
        result["enable"] = enable
        result["enable_lna"] = enable_lna
        result["supported_sample_rates"] = [48000, 96000, 192000]

        return result

    def configure_output_channel(
            self,
            channel,
            enable,
            sample_rate=Audio005002Def.AUDIO_SAMPLING_RATE):
        '''
        Configure output channel based on given parameters.

        Args:
            channel:      int, [1, 2], currently defined are 1(left), 2(right).
            enable:       boolean, indicating if given path should be enabled.
            sample_rate:  int, [192000], Sample rate to use for given channel.

        Exception:
            InvalidHardwareChannel() if provided channel is not 1 or 2.
            InvalidSampleRate() if sample_rate is bad.

        Return:
            dict, { "channel" : 1, "sample_rate" : 192000, "enable" : True,
                    "supported_sample_rates" : [192000] }
        '''
        if channel not in [1, 2]:
            raise InvalidHardwareChannel("channel is not 1 or 2")

        if sample_rate != Audio005002Def.AUDIO_SAMPLING_RATE:
            raise InvalidSampleRate("sample_rate is bad")

        if enable is True:
            self.i2s_tx_en_pin.set_level(Audio005002Def.AUDIO_OUTPUT_ENABLE)
            self.ram_out_pin.set_level(Audio005002Def.USE_RAM)
            self.output_enable = True
        else:
            self.i2s_tx_en_pin.set_level(Audio005002Def.AUDIO_OUTPUT_DISABLE)
            self.output_enable = False

        result = dict()
        result["channel"] = channel
        result["sample_rate"] = sample_rate
        result["enable"] = enable
        result["supported_sample_rates"] = [192000]

        return result

    def get_output_channel_configuration(self, channel):
        '''
        Returns the configuration of the specified channel.

        Args:
            channel: int, [1, 2], the desired channel.

        Exception:
            InvalidHardwareChannel() if provided channel is not 1 or 2

         Returns:
            dict, { "channel" : 1, "sample_rate" : 192000, "enable" : True,
                    "supported_sample_rates" : [192000] }
        '''
        if channel not in [1, 2]:
            raise InvalidHardwareChannel("channel is not 1 or 2")

        if self.output_enable is True:
            enable = True
        else:
            enable = False

        result = dict()
        result["channel"] = channel
        result["sample_rate"] = self.dac_sample_rate
        result["enable"] = enable
        result["supported_sample_rates"] = [192000]

        return result

    def _conversion_voltage_value(self, vref, data):
        '''
        Converts 32-bit data to voltage values.

        Args:
            vref:        float, unit mV.
            data:        list.

        Returns:
            list, voltage, unit mV.
        '''
        data >>= 8
        value = data & 0xffffff
        if (value >= pow(2, 23)):
            volt = value - pow(2, 24)
        else:
            volt = value

        volt = float(volt) / pow(2, 23)
        volt = volt * vref

        return volt

    def _read_voltage_points(self, channel, samples_per_channel, timeout=1000):
        '''
        Args:
            channel:             int, [1, 2].
            samples_per_channel: int. number of points read.
            timeout:             int, (>=0), default 1000, unit ms, execute timeout.

        Returns:
            list, unit mV, voltage.
        '''

        CHANNEL_LIST = {1: self.left_ch_id, 2: self.right_ch_id}
        code = channel - 1

        result, data, num, overflow = self.dma.read_channel_data(
            CHANNEL_LIST[channel], samples_per_channel * 4, timeout)
        if result == 0:
            data_list = data[:num]
        else:
            raise Audio005002Exception("get dma data error:{}".format(result))

        data_info = []
        data_len = len(data_list)
        for one_payload in range(0, data_len, 4):
            data = data_list[one_payload + 0] | data_list[one_payload + 1] << 8 | \
                data_list[one_payload + 2] << 16 | data_list[one_payload + 3] << 24

            if data & 0x05 != 0x00:
                continue
            data = self._conversion_voltage_value(self.vref[code], data)
            data_info.append(data)

        return data_info

    def read(self, channels, samples_per_channel, timeout=10.0):
        '''
        Args:
            channels:            int/list, [1, 2], the desired channel(s).
            samples_per_channel: int. number of points read.
            timeout:             float, (>=0), default 10.0, unit Second, execute timeout.

        Exception:
            InvalidHardwareChannel() if provided channel is not 1 or 2
            InvalidSampleCount() if samples_per_channel is of bad value
            InvalidTimeout() if invalid timeout value

        Returns:
            list, unit mv, ADC measurement data.
        '''
        if isinstance(channels, int) and channels not in [1, 2]:
            raise InvalidHardwareChannel("channel is not 1 or 2")
        if isinstance(channels, list) and (0 == len(channels)
                                           or len(channels) > 2):
            raise InvalidHardwareChannel("channel is not 1 or 2")
        if isinstance(
                channels,
                list) and 1 == len(channels) and channels[0] not in [1, 2]:
            raise InvalidHardwareChannel("channel is not 1 or 2")
        if isinstance(
                channels,
                list) and 2 == len(channels) and channels[0] not in [1, 2]:
            raise InvalidHardwareChannel("channel is not 1 or 2")
        if isinstance(
                channels,
                list) and 2 == len(channels) and channels[1] not in [1, 2]:
            raise InvalidHardwareChannel("channel is not 1 or 2")
        if samples_per_channel < Audio005002Def.MINI_NUM:
            raise InvalidSampleCount("samples_per_channel is of bad value")
        if timeout < 0 or timeout > 10.0:
            raise InvalidTimeout("invalid timeout value")

        data_info = []
        start_time = time.time()
        while True:
            try:
                if isinstance(channels, list):
                    for i in range(len(channels)):
                        data = self._read_voltage_points(
                            channels[i], samples_per_channel,
                            int(timeout * 1000))
                        if len(channels) == 2:
                            data_info.append(data)
                        else:
                            data_info = data
                else:
                    data_info = self._read_voltage_points(
                        channels, samples_per_channel, int(timeout * 1000))
                return data_info
            except Exception as e:
                if time.time() - start_time > timeout:
                    raise InvalidTimeout("Timeout")
                if e.__class__.__name__ == 'Audio005002Exception':
                    raise Audio005002Exception("{}".format(str(e)))

    def write(self, channel, data, repeat):
        '''
        Playback a waveform defined by data array for "repeat" times. Output sample_rate must be setup by means
        configure_output_channel.

        Args:
            channel:  int,  [1, 2], currently defined are 1(playback).
            data:     list, unit mv, list of data samples in volt.
            repeat:   int,  number of repeats.

        Returns:
            string, "done", execution successful.
        '''
        assert len(data) >= Audio005002Def.MINI_NUM
        assert len(data) <= Audio005002Def.OUTPUT_MAX_NUM
        assert repeat >= Audio005002Def.MINI_NUM

        w_data = []
        for volt in data:
            volt = int(volt / Audio005002Def.OUTPUT_VOLT_MAX * pow(2, 23))
            if volt < 0:
                volt = pow(2, 24) + volt

            volt = volt << 8
            w_data.append(volt)

        ram_addr = 0
        self.ram_signal.read_disable()
        self.ram_signal.set_number_of_repeats([repeat])
        start_time = time.time()
        while ram_addr != (len(w_data) - 1):
            self.ram_signal.disable()
            # Due to the existence of capacitance, it is necessary to delay
            # the capacitor discharge for more than 32ms after the loss of energy
            time.sleep(Audio005002Def.OUTPUT_RELAY_DELAY_S)
            self.ram_signal.enable()

            self.ram_signal.set_read_ramend_addr(len(w_data) - 1)
            self.ram_signal.set_tx_data(w_data)
            ram_addr = self.ram_signal.get_write_ramend_addr()

            if time.time() - start_time > Audio005002Def.TIME_OUT:
                raise Audio005002Exception("write timeout")

        self.ram_signal.read_enable()

        return "done"
Esempio n. 5
0
class Dazzler(MIXBoard):
    '''
    Dazzler function class.

    compatible = ['GQQ-AUD001004-000']

    The dazzler has a output sine function and one measure channel.
    It can measure signal's frequency, vpp, THD+N, THD.

    Args:
        i2c:                 instance(I2C),                  which is used to access eeprom and nct75, if not given,
                                                             will create eeprom and nct75 emulator.
        adc_rst_pin:         instance(GPIO),                 used to reset ADC CS5361. Setting low to reset CS5361,
                                                             seting high to enable CS5361.
        i2s_rx_en_pin:       instance(GPIO),                 used to enable fft analyzer module.
        dac_rst_pin:         instance(GPIO),                 used to reset IIS of the CS4398.
        i2s_tx_en_pin:       instance(GPIO),                 used to enable signal source module.
        sample_rate:         int,                            Use to config the CS5361 or CS4398, unit is Hz,
                                                             default 192000.
        ipcore:              instance(MIXAUT1SGR),              aggregated MIXAUT1SGR wrapper.

    Examples:
        i2c = I2C('/dev/i2c-1')
        aut1 = MIXAUT1SGR('/dev/MIX_AUT1_0')
        dazzler = Dazzler(i2c, ipcore=aut1)

        Example for measure, bandwith is 20000Hz, harmonic_num is 5:
            result = dazzler.measure(20000, 5)
            print("vpp={}, freq={}, thd={}, thdn={}, rms={}".format(result["vpp"], result["freq"], result["thd"],
                  result["thdn"], result["rms"]))

        Example for data upload:
            dma = Dma("/dev/MIX_DMA")
            dma.config_channel(0, 1024 * 1024)
            dma.enable_channel(0)
            dazzler.enable_upload()
            time.sleep(1)
            dazzler.disable_upload()
            data = dma.read_channel_all_data(0)
            dma.disable_channel(0)
            print(data)

    '''
    compatible = ['GQQ-AUD001004-000']

    rpc_public_api = [
        'module_init', 'enable_upload', 'disable_upload', 'measure',
        'enable_output', 'disable_output'
    ] + MIXBoard.rpc_public_api

    def __init__(self,
                 i2c,
                 adc_rst_pin=None,
                 i2s_rx_en_pin=None,
                 dac_rst_pin=None,
                 i2s_tx_en_pin=None,
                 sample_rate=192000,
                 ipcore=None):
        assert sample_rate > 0 and sample_rate <= DazzlerDef.MAX_SAMPLING_RATE
        if i2c is None:
            self.eeprom = EepromEmulator("cat24cxx_emulator")
            self.nct75 = NCT75Emulator("nct75_emulator")
        else:
            self.eeprom = CAT24C32(DazzlerDef.EEPROM_I2C_ADDR, i2c)
            self.nct75 = NCT75(DazzlerDef.SENSOR_I2C_ADDR, i2c)

        MIXBoard.__init__(self,
                          self.eeprom,
                          self.nct75,
                          cal_table=dazzler_calibration_info,
                          range_table=dazzler_range_table)

        if ipcore:
            if isinstance(ipcore, basestring):
                axi4 = AXI4LiteBus(ipcore, DazzlerDef.MIX_AUT1_REG_SIZE)
                self.ip = MIXAUT1SGR(axi4)
            else:
                self.ip = ipcore
            self.analyzer = self.ip.analyzer
            self.signal_source = self.ip.signal_source
            self.adc_rst_pin = adc_rst_pin or Pin(self.ip.gpio,
                                                  DazzlerDef.ADC_RESET_PIN)
            self.i2s_rx_en_pin = i2s_rx_en_pin or Pin(self.ip.gpio,
                                                      DazzlerDef.I2S_RX_EN_PIN)
            self.dac_rst_pin = dac_rst_pin or Pin(self.ip.gpio,
                                                  DazzlerDef.DAC_RESET_PIN)
            self.i2s_tx_en_pin = i2s_tx_en_pin or Pin(self.ip.gpio,
                                                      DazzlerDef.I2S_TX_EN_PIN)
        elif (not ipcore and not adc_rst_pin and not i2s_rx_en_pin
              and not dac_rst_pin and not i2s_tx_en_pin):
            self.analyzer = MIXFftAnalyzerSGEmulator(
                "mix_fftanalyzer_sg_emulator")
            self.signal_source = MIXSignalSourceSGEmulator(
                "mix_signalsource_sg_emulator")
            self.adc_rst_pin = Pin(None, DazzlerDef.ADC_RESET_PIN)
            self.i2s_rx_en_pin = Pin(None, DazzlerDef.I2S_RX_EN_PIN)
            self.dac_rst_pin = Pin(None, DazzlerDef.DAC_RESET_PIN)
            self.i2s_tx_en_pin = Pin(None, DazzlerDef.I2S_TX_EN_PIN)
        else:
            if ipcore:
                raise DazzlerException(
                    "parameter 'ipcore' can not be given at same time with 'analyzer', "
                    "'signal_source', 'adc_rst_pin', 'i2s_rx_en_pin', "
                    "'dac_rst_pin', 'i2s_tx_en_pin'")
            else:
                raise DazzlerException(
                    "parameter 'analyzer', 'signal_source', 'adc_rst_pin', "
                    "'i2s_rx_en_pin', 'dac_rst_pin' and 'i2s_tx_en_pin'"
                    " must be given at the same time")

        self.sample_rate = sample_rate

    def __del__(self):
        self.adc_rst_pin.set_level(0)
        self.i2s_rx_en_pin.set_level(0)
        self.dac_rst_pin.set_level(0)
        self.i2s_tx_en_pin.set_level(0)
        self.signal_source.close()

    def module_init(self):
        '''
        Dazzler init module, will reset dac/adc and i2s module.

        Returns:
            string, "done", api execution successful.

        '''
        self.adc_rst_pin.set_dir('output')
        self.dac_rst_pin.set_dir('output')
        self.i2s_rx_en_pin.set_dir('output')
        self.i2s_tx_en_pin.set_dir('output')

        # reset ADC
        self.adc_rst_pin.set_level(0)
        time.sleep(DazzlerDef.DELAY_S)
        self.adc_rst_pin.set_level(1)

        # reset DAC
        self.dac_rst_pin.set_level(0)
        time.sleep(DazzlerDef.DELAY_S)
        self.dac_rst_pin.set_level(1)

        self.i2s_rx_en_pin.set_level(1)

        self.load_calibration()

        return "done"

    def enable_upload(self):
        '''
        Dazzler upoad mode open.

        Control audio upload data of ADC when doing measurement.
        It's not necessary enable upload when doing measurement.
        Note that data transfered into DMA is 32bit each data, but high 24bit data is valid.
        Low 8bit data is invalid. The data format is twos complement.

        Returns:
            string, "done", api execution successful.
        '''
        self.analyzer.enable_upload()
        return "done"

    def disable_upload(self):
        '''
        Dazzler upoad mode close.

        Close data upload doesn't influence to measure.

        Returns:
            string, "done", api execution successful.
        '''
        self.analyzer.disable_upload()
        return "done"

    def measure(self, bandwidth_hz, harmonic_num, decimation_type=0xFF):
        '''
        Dazzler measure signal's Vpp, RMS, THD+N, THD.

        Args:
            bandwidth_hz:    int, [24~95977], Measure signal's limit bandwidth, unit is Hz. The frequency of the
                                              signal should not be greater than half of the bandwidth.
            harmonic_num:    int, [2-10], Use for measuring signal's THD.
            decimation_type: int, [1~255], Decimation for FPGA to get datas. If decimation is 0xFF, FPGA will
                                           choose one suitable number.

        Returns:
            dict, {"vpp":value, "freq":value, "thd":value, "thdn":value, "rms":value},
                  vpp, freq, thd, thdn, rms value.

        Examples:
            dazzler.measure(20000, 5, 0xff)
        '''
        assert bandwidth_hz == 'auto' or isinstance(bandwidth_hz, int)
        assert isinstance(harmonic_num, int) and harmonic_num > 0
        assert isinstance(decimation_type, int) and decimation_type > 0

        self.analyzer.disable()
        self.analyzer.enable()
        self.analyzer.analyze_config(self.sample_rate, decimation_type,
                                     bandwidth_hz, harmonic_num)
        self.analyzer.analyze()

        # calculate the actual vpp of HW by VREF
        vpp = self.analyzer.get_vpp() * DazzlerDef.AUDIO_ANALYZER_VREF
        # vpp = RMS * 2 * sqrt(2)
        rms = vpp / DazzlerDef.RMS_TO_VPP_RATIO

        rms = self.calibrate(DazzlerDef.MEASURE_CAL_ITEM, rms)
        vpp = rms * DazzlerDef.RMS_TO_VPP_RATIO

        result = dict()
        result["vpp"] = vpp
        result["freq"] = self.analyzer.get_frequency()
        result["thd"] = self.analyzer.get_thd()
        result["thdn"] = self.analyzer.get_thdn()
        result["rms"] = rms
        return result

    def enable_output(self, freq, rms):
        '''
        Dazzler output sine wave, differencial mode.

        Args:
            freq:    int, [10~100000], Ouput signal's frequency, unit is Hz.
            rms:     float, [0~2300], Ouput wave's RMS, unit is mV.

        Returns:
            string, "done", api execution successful.

        Examples:
            dazzler.enable_output(10000, 500)
        '''
        assert freq >= DazzlerDef.OUTPUT_FREQ_MIN
        assert freq <= DazzlerDef.OUTPUT_FREQ_MAX
        assert rms >= DazzlerDef.OUTPUT_RMS_MIN
        assert rms <= DazzlerDef.OUTPUT_RMS_MAX

        # enable I2S tx module
        self.i2s_tx_en_pin.set_level(1)

        self.signal_source.open()

        rms = self.calibrate(DazzlerDef.OUTPUT_CAL_ITEM, rms)
        # vpp = RMS * 2 * sqrt(2)
        vpp = rms * DazzlerDef.RMS_TO_VPP_RATIO
        # calculate vpp to vpp scale for FPGA
        vpp_scale = vpp * DazzlerDef.VPP_2_SCALE_RATIO / DazzlerDef.SIGNAL_SOURCE_VREF
        self.signal_source.set_signal_type(DazzlerDef.OUTPUT_WAVE)
        self.signal_source.set_signal_time(DazzlerDef.SIGNAL_ALWAYS_OUTPUT)
        self.signal_source.set_swg_paramter(self.sample_rate, freq, vpp_scale,
                                            DazzlerDef.OUTPUT_SIGNAL_DUTY)
        self.signal_source.output_signal()
        return "done"

    def disable_output(self):
        '''
        Dazzler close sine wave output.

        Returns:
            string, "done", api execution successful.

        Examples:
            dazzler.disable_output()
        '''
        self.signal_source.close()
        self.i2s_tx_en_pin.set_level(0)
        return "done"
Esempio n. 6
0
class MIXMagneto002SGR(object):
    '''
    MIXMagneto002SGR, support GPIO, FFT.

    Args:
        axi4_bus:            Instance(AXI4LiteBus)/string, axi4lite instance or dev path
        fft_data_cnt:        int,    get fft absolute data count, if not give, with get count from register.

    Example:
             magneto002 = MIXMagneto002SGR('/dev/MIX_Magneto002_SG_R_0')
    '''
    def __init__(self, axi4_bus, fft_data_cnt=None):
        if isinstance(axi4_bus, basestring):
            # device path; create axi4lite instance
            self.axi4_bus = AXI4LiteBus(axi4_bus, MIXMagneto002SGRDef.REG_SIZE)
        else:
            self.axi4_bus = axi4_bus

        self.fft_analyzer_axi4_bus = AXI4LiteSubBus(
            self.axi4_bus, MIXMagneto002SGRDef.MIX_FFT_ANAYLZER_IPCORE_ADDR,
            MIXMagneto002SGRDef.MIX_FFT_REG_SIZE)
        self.gpio_axi4_bus = AXI4LiteSubBus(
            self.axi4_bus, MIXMagneto002SGRDef.MIX_GPIO_IPCORE_ADDR,
            MIXMagneto002SGRDef.MIX_GPIO_REG_SIZE)

        self.gpio = MIXGPIOSG(self.gpio_axi4_bus)

        self.i2s_conf_0 = Pin(self.gpio, MIXMagneto002SGRDef.I2S_CONF0_BIT)
        self.i2s_conf_1 = Pin(self.gpio, MIXMagneto002SGRDef.I2S_CONF1_BIT)

        self.cs5361_ovfl = Pin(self.gpio, MIXMagneto002SGRDef.CS5361_OVFL_BIT)
        self.cs5361_rst = Pin(self.gpio, MIXMagneto002SGRDef.CS5361_RST_BIT)
        self.i2s_en = Pin(self.gpio, MIXMagneto002SGRDef.I2S_EN_BIT)
        self.analyzer = MIXFftAnalyzerSG(self.fft_analyzer_axi4_bus,
                                         fft_data_cnt)

        self.reset()

    def reset(self):
        self.i2s_conf_0.set_dir('output')
        self.i2s_conf_1.set_dir('output')
        self.cs5361_ovfl.set_dir('input')
        self.cs5361_rst.set_dir('output')
        self.i2s_en.set_dir('output')

        # disable ipcore
        self.i2s_en.set_level(0)

        self.config('left')

        # reset ic
        self.cs5361_rst.set_level(0)
        time.sleep(MIXMagneto002SGRDef.DELAY)
        self.cs5361_rst.set_level(1)

    def config(self, mode):
        '''
        Config.

        Args:
            mode:   string, ['left', 'right', 'differential'],  data mode.

        Example:
            magneto002.config('right')

        '''
        assert mode in MIXMagneto002SGRDef.I2S_DATA_MODE

        self.i2s_conf_0.set_level(MIXMagneto002SGRDef.I2S_DATA_MODE[mode][1])
        self.i2s_conf_1.set_level(MIXMagneto002SGRDef.I2S_DATA_MODE[mode][0])
        return 'done'

    def get_driver_version(self):
        return __version__
Esempio n. 7
0
class MIK002004(SGModuleDriver):
    '''
    MIK002004 is a high-pressure differential input digital audio module.

    compatible = ["GQQ-Q58K-5-040"]

    Args:
        i2c:                  instance(I2C), the instance of I2C bus. which will be used to used
                              to control eeprom, sensor and io expander.
        ipcore:               instance(MIXMIK002SGR)/string, the instance of Ipcore, which has 2 child IP,
                              MIXFftAnalyzerSG, MIXGPIOSG.
        analyzer:             instance(PLFFTAnalyzer)/string, if not given, will create emulator.
        adc_rst_pin:          instance(GPIO), used to reset IIS of the CS5381.
        adc_ovfl_l_pin:       instance(GPIO), used to get state of the ovfl_l pin for CS5381.
        i2s_left_select_pin:  instance(GPIO), used to enable IIS left channel module.
        i2s_right_select_pin: instance(GPIO), used to enable IIS right channel module.
        i2s_both_select_pin:  instance(GPIO), used to enable IIS both left & right channel module.
        tone_detect_pin:      instance(GPIO), used to get state of the tone detect pin.
        upload_enable_pin:    instance(GPIO), used to enable data upload to DMA directly.
        measure_enable_pin:   instance(GPIO), used to enable FFT measure data.
        sample_rate:          int, unit Hz, default 48000, used to configure the CS5381.

    Examples:
        # use non-aggregated IP
        i2c_bus = I2C('/dev/i2c-1')
        axi4 = AXI4LiteBus(analyzer_dev, 256)
        analyzer = MIXFftAnalyzerSG(axi4)
        adc_rst_pin = GPIO(88, 'output')
        adc_ovfl_l_pin = GPIO(93, 'output')
        i2s_left_select_pin = GPIO(88, 'output')
        i2s_right_select_pin = GPIO(89, 'output')
        i2s_both_select_pin = GPIO(87, 'output')
        tone_detect_pin = GPIO(94, 'output')
        upload_enable_pin = GPIO(97, 'output')
        measure_enable_pin = GPIO(95, 'output')
        warlock = MIK002004(i2c=i2c_bus, ipcore=None,
                          analyzer=analyzer, adc_rst_pin=adc_rst_pin,
                          adc_ovfl_l_pin=adc_ovfl_l_pin, i2s_left_select_pin=i2s_left_select_pin,
                          i2s_right_select_pin=i2s_right_select_pin,i2s_both_select_pin=i2s_both_select_pin,
                          tone_detect_pin=tone_detect_pin,upload_enable_pin=upload_enable_pin,
                          measure_enable_pin=measure_enable_pin, sample_rate=48000)

        # use MIXMIK002SGR aggregated IP
        i2c_bus = I2C('/dev/i2c-1')
        ipcore = MIXMIK002SGR('/dev/MIX_MIK002_SG_R')
        warlock = MIK002004(i2c=i2c_bus, ipcore=ipcore)

        # measure left channel input in china mode.
        warlock.headphone_out('1Vrms', 'CH')
        warlock.measure('left', 20000, 5)

        # measure xtalk signal in china mode.
        warlock.hp2mic_xtalk('CH')
        warlock.xtalk_measure('hp2mic')
    '''

    # launcher will use this to match driver compatible string and load driver if matched.
    compatible = ["GQQ-Q58K-5-040"]

    rpc_public_api = [
        'enable_upload', 'disable_upload', 'measure', 'xtalk_measure',
        'mikey_tone', 'get_tone_detect_state', 'set_sampling_rate',
        'get_sampling_rate', 'adc_hpf_state', 'headset_loopback', 'line_out',
        'headphone_out', 'hp2mic_xtalk', 'io_set', 'io_dir_set', 'adc_reset',
        'get_overflow_state', 'io_dir_read', 'io_read', 'start_record_data',
        'stop_record_data'
    ] + SGModuleDriver.rpc_public_api

    def __init__(self,
                 i2c,
                 ipcore=None,
                 analyzer=None,
                 adc_rst_pin=None,
                 adc_ovfl_l_pin=None,
                 i2s_left_select_pin=None,
                 i2s_right_select_pin=None,
                 i2s_both_select_pin=None,
                 tone_detect_pin=None,
                 upload_enable_pin=None,
                 measure_enable_pin=None,
                 sample_rate=48000):

        assert sample_rate > 0 and sample_rate <= MIK002004Def.MAX_SAMPLING_RATE

        if (i2c and analyzer and adc_rst_pin and adc_ovfl_l_pin
                and i2s_left_select_pin and i2s_right_select_pin
                and i2s_both_select_pin and tone_detect_pin
                and upload_enable_pin and measure_enable_pin):
            self.i2c = i2c

            if isinstance(analyzer, basestring):
                self.analyzer = MIXXtalkMeasureSG(analyzer)
            else:
                self.analyzer = analyzer

            self.adc_rst_pin = adc_rst_pin
            self.adc_ovfl_l_pin = adc_ovfl_l_pin
            self.i2s_left_select_pin = i2s_left_select_pin
            self.i2s_right_select_pin = i2s_right_select_pin
            self.i2s_both_select_pin = i2s_both_select_pin
            self.tone_detect_pin = tone_detect_pin
            self.upload_enable_pin = upload_enable_pin
            self.measure_enable_pin = measure_enable_pin
        elif (ipcore and i2c):
            self.i2c = i2c
            if isinstance(ipcore, basestring):
                axi4_bus = AXI4LiteBus(ipcore,
                                       MIK002004Def.MIX_MIK002_REG_SIZE)
                self.ipcore = MIXMIK002SGR(axi4_bus)
            else:
                self.ipcore = ipcore
            self.analyzer = self.ipcore.analyzer
            self.adc_rst_pin = Pin(self.ipcore.gpio, MIK002004Def.ADC_RST_PIN)
            self.adc_ovfl_l_pin = Pin(self.ipcore.gpio,
                                      MIK002004Def.ADC_OVFL_L_PIN)
            self.i2s_left_select_pin = Pin(self.ipcore.gpio,
                                           MIK002004Def.I2S_LEFT_SELECT_PIN)
            self.i2s_right_select_pin = Pin(self.ipcore.gpio,
                                            MIK002004Def.I2S_RIGHT_SELECT_PIN)
            self.i2s_both_select_pin = Pin(self.ipcore.gpio,
                                           MIK002004Def.I2S_BOTH_SELECT_PIN)
            self.tone_detect_pin = Pin(self.ipcore.gpio,
                                       MIK002004Def.TONE_DETECT_PIN)
            self.upload_enable_pin = Pin(self.ipcore.gpio,
                                         MIK002004Def.UPLOAD_ENABLE_PIN)
            self.measure_enable_pin = Pin(self.ipcore.gpio,
                                          MIK002004Def.MEASURE_ENABLE_PIN)
        else:
            raise MIK002004Exception("Parameter error")

        self.eeprom = CAT24C32(MIK002004Def.EEPROM_DEV_ADDR, i2c)
        self.nct75 = NCT75(MIK002004Def.SENSOR_DEV_ADDR, i2c)
        self.cat9555 = CAT9555(MIK002004Def.CAT9555_DEV_ADDR, i2c)

        super(MIK002004, self).__init__(self.eeprom,
                                        self.nct75,
                                        range_table=warlock_range_table)

        self.sampling_rate = sample_rate
        self.scope = ""
        self.mode = ""

    def post_power_on_init(self, timeout=MIK002004Def.DEFAULT_TIMEOUT):
        '''
        Init MIK002004 module to a know harware state.

        This function will reset adc and i2s module.

        Args:
            timeout:      float, (>=0), default 1, unit Second, execute timeout.

        '''
        self.reset(timeout)

    def reset(self, timeout=MIK002004Def.DEFAULT_TIMEOUT):
        '''
        Reset the instrument module to a know hardware state.

        Args:
            timeout:      float, (>=0), default 1, unit Second, execute timeout.


        Returns:
            string, "done", if execute successfully.

        '''
        start_time = time.time()

        while True:
            try:
                self.adc_rst_pin.set_dir(MIK002004Def.IO_OUTPUT_DIR)
                self.adc_ovfl_l_pin.set_dir(MIK002004Def.IO_INPUT_DIR)
                self.i2s_left_select_pin.set_dir(MIK002004Def.IO_OUTPUT_DIR)
                self.i2s_right_select_pin.set_dir(MIK002004Def.IO_OUTPUT_DIR)
                self.i2s_both_select_pin.set_dir(MIK002004Def.IO_OUTPUT_DIR)
                self.tone_detect_pin.set_dir(MIK002004Def.IO_INPUT_DIR)
                self.upload_enable_pin.set_dir(MIK002004Def.IO_OUTPUT_DIR)
                self.measure_enable_pin.set_dir(MIK002004Def.IO_OUTPUT_DIR)

                # reset ADC
                self.adc_reset()

                self.i2s_left_select_pin.set_level(0)
                self.i2s_right_select_pin.set_level(0)
                self.i2s_both_select_pin.set_level(0)
                self.measure_enable_pin.set_level(0)
                self.upload_enable_pin.set_level(1)

                self.cat9555.set_ports([0x00, 0x1f])
                self.cat9555.set_pins_dir([0x00, 0xf0])

                self._get_default_sampling_rate()
                return "done"
            except Exception as e:
                if time.time() - start_time > timeout:
                    raise MIK002004Exception("Timeout: {}".format(e.message))
        return "done"

    def pre_power_down(self, timeout=MIK002004Def.DEFAULT_TIMEOUT):
        '''
        Put the hardware in a safe state to be powered down.

        Args:
            timeout:      float, (>=0), default 1, unit Second, execute timeout.
        '''
        start_time = time.time()

        while True:
            try:
                self.adc_rst_pin.set_level(0)
                self.i2s_left_select_pin.set_level(0)
                self.i2s_right_select_pin.set_level(0)
                self.i2s_both_select_pin.set_level(0)
                return
            except Exception as e:
                if time.time() - start_time > timeout:
                    raise MIK002004Exception("Timeout: {}".format(e.message))

    def start_record_data(self, channel):
        '''
        MIK002004 start collect the data and upload to DMA directly.

        Args:
            channel:         string, ['left', 'right', 'both'].

        Returns:
            string, 'done', api execution successful.

        Examples:
            warlock.start_record_data('left')

        '''
        assert channel in MIK002004Def.AUDIO_CHANNEL_LIST + ["both"]
        self._audio_channel_select(channel)
        time.sleep(MIK002004Def.GPIO_DELAY_MS / 1000.0)
        return "done"

    def stop_record_data(self):
        '''
        MIK002004 stop collect the data.

        Returns:
            string, 'done', api execution successful.

        Examples:
            warlock.stop_record_data()

        '''
        self.i2s_left_select_pin.set_level(0)
        self.i2s_right_select_pin.set_level(0)
        self.i2s_both_select_pin.set_level(0)
        time.sleep(MIK002004Def.GPIO_DELAY_MS / 1000.0)
        return "done"

    def _get_default_sampling_rate(self):
        io_states = self.cat9555.get_ports()
        target_pin = io_states[1] & MIK002004Def.sample_rate_pin_mask
        if target_pin in MIK002004Def.sampling_rate_select:
            self.sampling_rate = MIK002004Def.sampling_rate_select[target_pin]
        else:
            self.set_sampling_rate(self.sampling_rate)

    def _set_function_path(self, config, scope):
        '''
        MIK002004 set function path

        Args:
            config:     string, ['mode_select', 'range_select','mikey_tone',
                                 'short_circuit_detect', 'extra'].
            scope:       string, ['GB','CH','HP_1Vrms','HP_3.5Vrms','line_out','s1_enable',
                                 's2_enable','s3_enable','all_disable','MIC_S0_enable',
                                 'MIC_S0_disable','enable','disable','hp2mic_xtalk'].

        Returns:
            string, 'done', api execution successful.

        Examples:
            warlock._set_function_path('mode_select', 'GB')

        '''
        assert config in warlock_function_info
        assert scope in warlock_function_info[config]

        bits = warlock_function_info[config][scope]['bits']
        for bit in bits:
            self.cat9555.set_pin(bit[0], bit[1])

        time.sleep(MIK002004Def.RELAY_DELAY_MS / 1000.0)

    def _volt_to_target_unit(self, scope, volt):
        '''
        MIK002004 get target unit value from measured voltage.

        Args:
            scope:      string, the range of channel measure.
            volt:       float, the measured voltage.

        Returns:
            float, value.

        '''
        assert scope in warlock_function_info['range_select']

        return volt * warlock_function_info['range_select'][scope][
            'coefficient']

    def _audio_channel_select(self, channel):
        '''
        MIK002004 cs5381 chip channel select.

        Args:
            channel:  string, ['left','right','both'], Use for select which channel to measure.

        Returns:
            string, 'done', api execution successful.

        Examples:
            warlock._audio_channel_select('left')

        '''
        if channel == 'left':
            self.i2s_right_select_pin.set_level(0)
            self.i2s_both_select_pin.set_level(0)
            self.i2s_left_select_pin.set_level(1)
        elif channel == 'right':
            self.i2s_left_select_pin.set_level(0)
            self.i2s_both_select_pin.set_level(0)
            self.i2s_right_select_pin.set_level(1)
        else:
            self.i2s_both_select_pin.set_level(1)
        return "done"

    def enable_upload(self):
        '''
        MIK002004 upoad mode open.

        Control audio upload data of ADC when doing measurement. It's not necessary
        enable upload when doing measurement. Note that data transfered into DMA is 32bit each data,
        but high 24bit data is valid. Low 8bit data is invalid. The data format is twos complement.

        Returns:
            string, 'done', api execution successful.

        '''
        self.analyzer.enable_upload()
        return "done"

    def disable_upload(self):
        '''
        MIK002004 upoad mode close. Close data upload doesn't influence to measure.

        Returns:
            string, 'done', api execution successful.

        '''
        self.analyzer.disable_upload()
        return "done"

    def set_sampling_rate(self, sampling_rate):
        '''
        MIK002004 set sampling rate.

        Args:
            sampling_rate:     int, [192000, 96000, 48000], unit Hz, adc measure sampling rate.

        Returns:
            string, 'done', api execution successful.

        Examples:
           warlock.set_sampling_rate(96000)

        '''
        assert str(
            sampling_rate) + "Hz" in warlock_function_info["adc_sample_rate"]
        io_dir_state = self.cat9555.get_pins_dir()
        if io_dir_state[1] & MIK002004Def.sample_rate_pin_mask != 0:
            self.cat9555.set_pins_dir([
                io_dir_state[0],
                io_dir_state[1] & ~MIK002004Def.sample_rate_pin_mask
            ])
        self._set_function_path("adc_sample_rate", str(sampling_rate) + "Hz")
        self.sampling_rate = sampling_rate if isinstance(
            sampling_rate, int) else int(sampling_rate)
        return "done"

    def get_sampling_rate(self):
        '''
        MIK002004 get sampling rate.

        Returns:
            int, unit Hz.

        Examples:
           warlock.get_sampling_rate()

        '''
        return self.sampling_rate

    def headset_loopback(self, mode):
        '''
        MIK002004 set headset_loopback function.

        Args:
            mode:      string, ['GB', 'CH'], GB mean global mode, CH mean china mode.

        Returns:
            string, 'done', api execution successful.

        Examples:
            warlock.headset_loopback('CH')

        '''
        assert mode in warlock_function_info["mode_select"]
        self._set_function_path("extra", "headset_loopback")
        self._set_function_path("mode_select", mode)
        return "done"

    def headphone_out(self, range_name, mode):
        '''
        MIK002004 set headphone_out function.

        Args:
            range_name: string, ['1Vrms', '3.5Vrms'].
            mode:       string, ['GB', 'CH'], GB mean global mode, CH mean china mode.

        Returns:
            string, 'done', api execution successful.

        Examples:
            warlock.headphone_out('1Vrms', 'CH')

        '''
        assert ("HP_" + range_name) in warlock_function_info["range_select"]
        assert mode in warlock_function_info["mode_select"]
        self._set_function_path("range_select", "HP_" + range_name)
        self._set_function_path("mode_select", mode)
        self.mode = mode
        self.scope = "HP_" + range_name
        return "done"

    def line_out(self):
        '''
        MIK002004 set line_out function.

        Returns:
            string, 'done', api execution successful.

        Examples:
            warlock.line_out()

        '''
        self._set_function_path("range_select", "line_out")
        self.scope = "line_out"
        return "done"

    def hp2mic_xtalk(self, mode, range_name="32ohm"):
        '''
        MIK002004 set hp2mic_xtalk function.

        Args:
            mode:       string, ['GB', 'CH'], GB mean global mode, CH mean china mode.
            range_name: string, ["32ohm", "400ohm"]

        Returns:
            string, 'done', api execution successful.

        Examples:
            warlock.hp2mic_xtalk('CH')

        '''
        assert mode in warlock_function_info["mode_select"]

        self._set_function_path("hp2mic_xtalk", range_name)
        self._set_function_path("mode_select", mode)
        self.mode = mode
        self.scope = "HP_1Vrms"
        return "done"

    def io_set(self, io_list):
        '''
        MIK002004 set io state, this is a debug function.

        Args:
            io_list:   list, [[pin,state],[pin,state]], pin [0~15], state [0,1].

        Returns:
            string, 'done', api execution successful.

        Examples:
            warlock.io_set([[0,1],[1,1]])

        '''
        assert isinstance(io_list, list)
        assert all([isinstance(io, list) and len(io) == 2 for io in io_list])
        assert all(
            [io[0] in xrange(16) and io[1] in xrange(2) for io in io_list])
        for io in io_list:
            self.cat9555.set_pin(io[0], io[1])
        return "done"

    def io_dir_set(self, io_list):
        '''
        MIK002004 set io direction, this is a debug function.

        Args:
            io_list:   list, [[pin,state],[pin,state]], pin [0~15], state ['input','output'].

        Returns:
            string, 'done', api execution successful.

        Examples:
            warlock.io_dir_set([[0,'output'],[1,'output']])

        '''
        assert isinstance(io_list, list)
        assert all([isinstance(io, list) and len(io) == 2 for io in io_list])
        assert all([
            io[0] in xrange(16) and io[1] in ['output', 'input']
            for io in io_list
        ])
        for io in io_list:
            self.cat9555.set_pin_dir(io[0], io[1])
        return 'done'

    def io_read(self, io_list):
        '''
        MIK002004 set io state, this is a debug function.

        Args:
            io_list:   list, [pinx,pinx,… ,pinx], pin x mean [0~15].

        Returns:
            list, [[pin,state],[pin,state]], pin [0~15], state [0,1].

        Examples:
            warlock.read_pin([0,1,2,7])

        '''
        assert isinstance(io_list, list)
        assert all([isinstance(io, int) for io in io_list])
        assert all([0 <= io <= 15 for io in io_list])
        data_list = []
        for io in io_list:
            data_list.append([io, self.cat9555.get_pin(io)])

        return data_list

    def io_dir_read(self, io_list):
        '''
        MIK002004 set io direction, this is a debug function.

        Args:
            io_list:   list, [pinx,pinx,… ,pinx], pin x mean [0~15].

        Returns:
            list, [[pin,state],[pin,state]], pin [0~15], state ['input','output'].

        Examples:
            warlock.read_pin_dir([0,1])

        '''
        assert isinstance(io_list, list)
        assert all([isinstance(io, int) for io in io_list])
        assert all([0 <= io <= 15 for io in io_list])
        data_list = []
        for io in io_list:
            data_list.append([io, self.cat9555.get_pin_dir(io)])
        return data_list

    def adc_reset(self):
        '''
        MIK002004 reset adc.

        Returns:
            string, 'done', api execution successful.

        Examples:
            warlock.adc_reset()

        '''
        self.adc_rst_pin.set_level(0)
        time.sleep(MIK002004Def.RST_DELAY_MS / 1000.0)
        self.adc_rst_pin.set_level(1)
        return "done"

    def measure(self,
                channel,
                bandwidth_hz=50000,
                harmonic_num=5,
                decimation_type=0xFF):
        '''
        MIK002004 measure signal's Vpp, RMS, THD+N, THD.

        Args:
            channel:         string, ['left', 'right'].
            bandwidth_hz:    int, [50~95977], default 50000,  unit Hz, Measure signal's limit bandwidth,
                             The frequency of the signal should not be greater than half of the bandwidth.
            harmonic_num:    int, [2~10], default 5, Use for measuring signal's THD.
            decimation_type: int, [1~255], default 255, Decimation for FPGA to get datas,
                             If decimation is 0xFF, FPGA will choose one suitable number.

        Returns:
            dict, {'vpp': value, 'freq': value, 'thd': value, 'thdn': value, 'rms': value),
                  dict with vpp, freq, thd, thdn, rms.

        Examples:
            result = warlock.measure('left', 20000, 5, 255)
            print result['frequency'], result['vpp'], result['thdn'], result['thd'], result['rms']

        '''
        assert channel in MIK002004Def.AUDIO_CHANNEL_LIST

        assert bandwidth_hz == MIK002004Def.BANDWIDTH_AUTO or isinstance(
            bandwidth_hz, int)
        assert MIK002004Def.BANDWIDTH_MIN <= bandwidth_hz <= MIK002004Def.BANDWIDTH_MAX
        assert isinstance(harmonic_num, int) and\
            MIK002004Def.HARMONIC_CNT_MIN <= harmonic_num <= MIK002004Def.HARMONIC_CNT_MAX
        assert isinstance(decimation_type, int) and\
            MIK002004Def.DECIMATION_TYPE_MIN <= decimation_type <= MIK002004Def.DECIMATION_TYPE_MAX

        self.upload_enable_pin.set_level(0)
        self.stop_record_data()
        self._audio_channel_select("both")
        self.measure_enable_pin.set_level(1)

        self.analyzer.disable()
        self.analyzer.enable()
        self.analyzer.measure_select(channel, MIK002004Def.NORMAL_ANALYZE_MODE)
        self.analyzer.analyze_config(self.sampling_rate, decimation_type,
                                     bandwidth_hz, harmonic_num)
        self.analyzer.analyze()

        # calculate the actual vpp of HW by VREF
        vpp = self.analyzer.get_vpp() * MIK002004Def.AUDIO_ANALYZER_VREF
        vpp = self._volt_to_target_unit(self.scope, vpp)
        self.upload_enable_pin.set_level(1)
        # vpp = RMS * 2 * sqrt(2)
        rms = vpp / MIK002004Def.RMS_TO_VPP_RATIO
        range_name = (channel + "_" +
                      MIK002004Def.RMS_LIST[self.scope]).upper()
        rms = self.calibrate(range_name, rms)

        vpp = rms * MIK002004Def.RMS_TO_VPP_RATIO

        result = dict()
        result["vpp"] = vpp
        result["freq"] = self.analyzer.get_frequency()
        result["thd"] = self.analyzer.get_thd()
        result["thdn"] = self.analyzer.get_thdn()
        result["snr"] = -1 * result["thdn"]
        result["rms"] = rms

        return result

    def _measure(self,
                 channel,
                 bandwidth_hz=50000,
                 harmonic_num=5,
                 decimation_type=1):
        '''
        MIK002004 _measure signal's RMS for Crosstalk measure

        Args:
            channel:         string, ['left', 'right'].
            bandwidth_hz:    int, [50~95977], default 50000,  unit Hz, Measure signal's limit bandwidth,
                             The frequency of the signal should not be greater than half of the bandwidth.
            harmonic_num:    int, [2~10], default 5, Use for measuring signal's THD.
            decimation_type: int, [1~255], default 1, Decimation for FPGA to get datas,
                             If decimation is 0xFF, FPGA will choose one suitable number.

        Returns:
            float.

        Examples:
            result = warlock._measure('left', 20000, 5, 1)
            print result

        '''
        assert channel in MIK002004Def.AUDIO_CHANNEL_LIST

        assert bandwidth_hz == MIK002004Def.BANDWIDTH_AUTO or isinstance(
            bandwidth_hz, int)
        assert MIK002004Def.BANDWIDTH_MIN <= bandwidth_hz <= MIK002004Def.BANDWIDTH_MAX
        assert isinstance(harmonic_num, int) and\
            MIK002004Def.HARMONIC_CNT_MIN <= harmonic_num <= MIK002004Def.HARMONIC_CNT_MAX
        assert isinstance(decimation_type, int) and\
            MIK002004Def.DECIMATION_TYPE_MIN <= decimation_type <= MIK002004Def.DECIMATION_TYPE_MAX

        self.upload_enable_pin.set_level(0)
        self.stop_record_data()
        self._audio_channel_select("both")
        self.measure_enable_pin.set_level(1)
        if channel == "left":
            self.analyzer.disable()
            self.analyzer.enable()
            self.analyzer.analyze_config(self.sampling_rate, decimation_type,
                                         bandwidth_hz, harmonic_num)
        self.analyzer.measure_select(channel, MIK002004Def.XTALK_ANALYZE_MODE)
        self.analyzer.analyze()
        # calculate the actual vpp of HW by VREF
        vpp = self.analyzer.get_vpp() * MIK002004Def.AUDIO_ANALYZER_VREF
        vpp = self._volt_to_target_unit(self.scope, vpp)
        self.upload_enable_pin.set_level(1)
        # vpp = RMS * 2 * sqrt(2)
        rms = vpp / MIK002004Def.RMS_TO_VPP_RATIO
        range_name = (channel + "_" +
                      MIK002004Def.RMS_LIST[self.scope]).upper()
        rms = self.calibrate(range_name, rms)

        result = dict()
        result["vpp"] = vpp
        result["freq"] = self.analyzer.get_frequency()
        result["thd"] = self.analyzer.get_thd()
        result["thdn"] = self.analyzer.get_thdn()
        result["snr"] = -1 * result["thdn"]
        result["rms"] = rms

        return result

    def xtalk_measure(self,
                      channel,
                      bandwidth_hz=50000,
                      harmonic_num=5,
                      decimation_type=1):
        '''
        MIK002004 Crosstalk mode measure.

        Args:
            channel:    string, ['hp2mic', 'hpl2r', 'hpr2l'].
            bandwidth_hz:    int, [50~95977], default 50000,  unit Hz, Measure signal's limit bandwidth,
                             The frequency of the signal should not be greater than half of the bandwidth.
            harmonic_num:    int, [2~10],     default 5, Use for measuring signal's THD.
            decimation_type: int, [1~255], default 1, Decimation for FPGA to get data,
                             If decimation is 0xFF, FPGA will choose one suitable number.

        Returns:
            list, [value, db].

        Examples:
            warlock.xtalk_measure('hp2mic')

        '''
        assert channel in ["hp2mic", "hpl2r", "hpr2l"]
        left_result = self._measure("left", bandwidth_hz, harmonic_num,
                                    decimation_type)
        right_result = self._measure("right", bandwidth_hz, harmonic_num,
                                     decimation_type)
        Va = left_result["rms"]
        Vb = right_result["rms"]

        if channel == "hpr2l":
            xtalk = 20 * math.log(Va / Vb, 10)
        else:
            xtalk = 20 * math.log(Vb / Va, 10)
        return {"xtalk": xtalk, "left": left_result, "right": right_result}

    def mikey_tone(self, freq="default", mode="GB"):
        '''
        MIK002004 set signal output.

        Args:
            freq:      string, ['S1', 'S2', 'S3', 'S0', 'default'], 'default' mean (S0, S1, S2, S3)=1.
            mode:      string, ['GB', 'CH'], default 'GB', GB mean global mode, CH mean china mode.

        Returns:
            string, 'done', api execution successful.

        Examples:
            warlock.mikey_tone('S1', 'CH')

        '''
        assert freq in warlock_function_info["mikey_tone"]
        assert mode in warlock_function_info["mode_select"]

        self._set_function_path("mode_select", mode)
        self._set_function_path("mikey_tone", freq)
        return "done"

    def get_tone_detect_state(self):
        '''
        MIK002004 get tone detect state.

        Returns:
            int, [1,0], 1 mean high level, 0 mean low level.

        Examples:
            warlock.get_tone_detect_state()

        '''
        return self.tone_detect_pin.get_level()

    def get_overflow_state(self):
        '''
        MIK002004 get overflow pin state.

        Returns:
            int, [1,0], 1 mean high level, 0 mean low level.

        Examples:
            warlock.get_overflow_state()

        '''
        return self.adc_ovfl_l_pin.get_level()

    def adc_hpf_state(self, state):
        '''
        MIK002004 set adc hpf pin.

        Args:
            state:     string, ['enable', 'disable'].

        Returns:
            string, 'done', api execution successful.

        Examples:
           warlock.adc_hpf_state('enable')

        '''
        assert state in ["enable", "disable"]
        if self.cat9555.get_pin_dir(
                MIK002004Def.ADC_HPF_PIN) != MIK002004Def.IO_OUTPUT_DIR:
            self.cat9555.set_pin_dir(MIK002004Def.ADC_HPF_PIN,
                                     MIK002004Def.IO_OUTPUT_DIR)

        self._set_function_path("ADC_HPF", state)
        return "done"

    def get_driver_version(self):
        '''
        Get MIK002004 driver version.

        Returns:
            string, current driver version.
        '''
        return __version__
Esempio n. 8
0
class BeastBase(SGModuleDriver):
    '''
    Base class of Beast and BeastCompatible.
    Providing common Beast methods.

    Args:
        i2c:              instance(I2C),              used to control eeprom and nct75 sensor.
        range_ctrl_pin:   instance(GPIO),             used to control Beast range.
        dcac_ctrl_pin:    instance(GPIO),             used to control DC/AC mode.
        output_ctrl_pin:  instance(GPIO),             used to control AD5231 disable/enable state.
        pl_signal_meter:  instance(PLSignalMeter),    used to access PLSignalMeter ipcore.
        ipcore:           instance(MIXDAQT2SGR),      used to access PLGPIO, PLSignalMeter ipcore.
        eeprom_dev_addr:  int,                        eeprom device address.
        sensor_dev_addr:  int,                        NCT75 device address.

    '''

    rpc_public_api = [
        'select_range', 'enable_continuous_measure',
        'disable_continuous_measure', 'set_measure_mask', 'frequency_measure',
        'vpp_measure', 'rms_measure', 'level', 'adc_enable', 'adc_disable'
    ] + SGModuleDriver.rpc_public_api

    def __init__(self,
                 i2c,
                 range_ctrl_pin=None,
                 dcac_ctrl_pin=None,
                 output_ctrl_pin=None,
                 pl_signal_meter=None,
                 ipcore=None,
                 eeprom_dev_addr=BeastDef.EEPROM_DEV_ADDR,
                 sensor_dev_addr=BeastDef.SENSOR_DEV_ADDR):

        if i2c is None:
            super(BeastBase, self).__init__(None,
                                            None,
                                            range_table=beast_range_table)
        else:
            self.eeprom = CAT24C32(eeprom_dev_addr, i2c)
            self.nct75 = NCT75(sensor_dev_addr, i2c)
            super(BeastBase, self).__init__(self.eeprom,
                                            self.nct75,
                                            range_table=beast_range_table)

        if i2c and range_ctrl_pin and dcac_ctrl_pin and output_ctrl_pin and pl_signal_meter and not ipcore:
            self.range_ctrl_pin = range_ctrl_pin
            self.dcac_ctrl_pin = dcac_ctrl_pin
            self.output_ctrl_pin = output_ctrl_pin
            if isinstance(pl_signal_meter, basestring):
                axi4_bus = AXI4LiteBus(pl_signal_meter,
                                       BeastDef.SIGNAL_METER_REG_SIZE)
                self.pl_signal_meter_device = MIXSignalMeterSG(axi4_bus)
            else:
                self.pl_signal_meter_device = pl_signal_meter
        elif i2c and not range_ctrl_pin and not dcac_ctrl_pin and not output_ctrl_pin and not pl_signal_meter \
                and ipcore:
            if isinstance(ipcore, basestring):
                axi4_bus = AXI4LiteBus(ipcore, BeastDef.MIX_DAQT2_REG_SIZE)
                self.mix_daqt2 = MIXDAQT2SGR(axi4_bus,
                                             use_signal_meter1=False,
                                             use_spi=False,
                                             use_gpio=True)
            else:
                self.mix_daqt2 = ipcore
            self.pl_signal_meter_device = self.mix_daqt2.signal_meter0
            self.range_ctrl_pin = Pin(self.mix_daqt2.gpio,
                                      BeastDef.BEAST_RANGE_CTRL_PIN)
            self.dcac_ctrl_pin = Pin(self.mix_daqt2.gpio,
                                     BeastDef.BEAST_DCAC_CTRL_PIN)
            self.output_ctrl_pin = Pin(self.mix_daqt2.gpio,
                                       BeastDef.BEAST_OUTPUT_CTRL_PIN)
        elif i2c and range_ctrl_pin and dcac_ctrl_pin and output_ctrl_pin and not pl_signal_meter and ipcore:
            if isinstance(ipcore, basestring):
                axi4_bus = AXI4LiteBus(ipcore, BeastDef.MIX_DAQT2_REG_SIZE)
                self.mix_daqt2 = MIXDAQT2SGR(axi4_bus,
                                             use_signal_meter1=False,
                                             use_spi=False,
                                             use_gpio=False)
            else:
                self.mix_daqt2 = ipcore
            self.pl_signal_meter_device = self.mix_daqt2.signal_meter0
            self.range_ctrl_pin = range_ctrl_pin
            self.dcac_ctrl_pin = dcac_ctrl_pin
            self.output_ctrl_pin = output_ctrl_pin
        else:
            raise BeastBaseException("Invalid parameter, please check")

        self._mask = 0

    def post_power_on_init(self, timeout=BeastDef.TIME_OUT):
        '''
        Init Beast module to a know harware state.

        This function will set gpio pin io direction to output and set default range to '20Vpp'

        Args:
            timeout:      float, unit Second, execute timeout

        '''
        self.reset(timeout)

    def pre_power_down(self, timeout=BeastDef.TIME_OUT):
        '''
        Put the hardware in a safe state to be powered down.

        This function will set gpio pin io direction to output.

        Args:
            timeout:      float, unit Second, execute timeout

        '''
        self.range_ctrl_pin.set_dir(BeastDef.PIN_OUTPUT_DIRECTION)
        self.dcac_ctrl_pin.set_dir(BeastDef.PIN_OUTPUT_DIRECTION)
        self.output_ctrl_pin.set_dir(BeastDef.PIN_OUTPUT_DIRECTION)

    def reset(self, timeout=BeastDef.TIME_OUT):
        '''
        Reset the instrument module to a know hardware state.

        Args:
            timeout:      float, unit Second, execute timeout.

        '''
        self.range_ctrl_pin.set_dir(BeastDef.PIN_OUTPUT_DIRECTION)
        self.dcac_ctrl_pin.set_dir(BeastDef.PIN_OUTPUT_DIRECTION)
        self.output_ctrl_pin.set_dir(BeastDef.PIN_OUTPUT_DIRECTION)
        # Use max range is 20 Vpp as initial range
        self.select_range('AC_VOLT', 20)

    def get_driver_version(self):
        '''
        Get beast driver version.

        Returns:
            string, current driver version.

        '''
        return __version__

    def select_range(self, signal_type, value):
        '''
        Select Beast measurement range. All support range shown as below.

        Args:
            signal_type:    string, ['DC_VOLT', 'AC_VOLT'], Range signal_type string.
            value:          int, [2, 20], Range value.

        Returns:
            string, "done", api execution successful.

        '''
        assert signal_type in ["DC_VOLT", "AC_VOLT"]
        assert value in BeastDef.RANGE_VALUE_LIST

        if signal_type == "DC_VOLT":
            self.dcac_ctrl_pin.set_level(BeastDef.IO_LOW_LEVEL)
        else:
            self.dcac_ctrl_pin.set_level(BeastDef.IO_HIGH_LEVEL)

        if value == BeastDef.RANGE_VALUE_LIST[0]:
            self.board_gain = BeastDef.GAIN1
            self.range_ctrl_pin.set_level(BeastDef.IO_LOW_LEVEL)
        else:
            self.board_gain = BeastDef.GAIN10
            self.range_ctrl_pin.set_level(BeastDef.IO_HIGH_LEVEL)
        self.signal_type = signal_type
        return 'done'

    def enable_continuous_measure(self):
        '''
        Beast enable_continuous_measure function. board data will be copyed to dma when this function called.

        Returns:
            string, "done", api execution successful.

        '''
        self.adc_enable()
        self.pl_signal_meter_device.open()
        self.pl_signal_meter_device.enable_upframe(BeastDef.UPFRAME_MODE)

        return 'done'

    def disable_continuous_measure(self):
        '''
        Beast disable_continuous_measure function. Data will disable upload, when this function called.

        Returns:
            string, "done", api execution successful.

        '''
        self.adc_disable()
        self.pl_signal_meter_device.disable_upframe()
        self.pl_signal_meter_device.close()

        return 'done'

    def set_measure_mask(self, mask=0):
        '''
        Beast set signal meter measure mask.

        Args:
            mask:    int, default 0, mask bit value shown as below.

            +---------------+-------------------+
            |   mask        |       function    |
            +===============+===================+
            | bit[0:3]      | Reserved          |
            +---------------+-------------------+
            | bit[4]        | Frequency mask    |
            +---------------+-------------------+
            | bit[5]        | Duty mask         |
            +---------------+-------------------+
            | bit[6]        | Vpp mask          |
            +---------------+-------------------+
            | bit[7]        | rms mask          |
            +---------------+-------------------+

        Returns:
            string, "done", api execution successful.

        '''
        self._mask = mask
        return "done"

    def frequency_measure(self, duration, measure_type="LP"):
        '''
        Measure input signal frequncy and duty in a defined duration.

        Args:
            duration:      int, [1~2000], millisecond time to measure frequncy.
            measure_type:  string, ["HP", "LP"], default "LP", type of measure.

        Returns:
            list, [value, value], result value contain freq and duty, units are Hz, %.

        Examples:
            # adc_enable() and adc_disable() will operate on Pin
            # which might belongs to another i2c Mux port from on-board eeprom so put outside.
            beast.adc_enable()
            ret = beast.frequency_measure(10)
            beast.adc_disable()
            # ret will be list: [freq, duty]

        '''
        assert isinstance(duration, int) and duration > 0 and duration <= 2000
        assert measure_type in ["HP", "LP"]

        self.pl_signal_meter_device.open()
        self.pl_signal_meter_device.set_vpp_interval(
            BeastDef.BEAST_VPP_DURATION)
        self.pl_signal_meter_device.start_measure(duration,
                                                  BeastDef.BEAST_SAMPLE_RATE,
                                                  self._mask)
        freq = self.pl_signal_meter_device.measure_frequency(measure_type)
        duty = self.pl_signal_meter_device.duty()
        self.pl_signal_meter_device.close()

        return [freq, duty]

    def vpp_measure(self, duration):
        '''
        Measure input signal vpp, max and min voltage in a defined duration.

        Args:
            duration:   int, [1~2000], millisecond time to measure vpp.

        Returns:
            list, [value, value, value], result value contain vpp, max and min voltage, unit is mV.

        Examples:
            # adc_enable() and adc_disable() will operate on Pin
            # which might belongs to another i2c Mux port from on-board eeprom so put outside.
            beast.adc_enable()
            ret = beast.vpp_measure(10)
            beast.adc_disable()
            # ret will be list: [vpp, max, min]

        '''
        assert isinstance(duration, int) and duration > 0 and duration <= 2000
        self.pl_signal_meter_device.open()
        self.pl_signal_meter_device.set_vpp_interval(
            BeastDef.BEAST_VPP_DURATION)
        self.pl_signal_meter_device.start_measure(duration,
                                                  BeastDef.BEAST_SAMPLE_RATE,
                                                  self._mask)
        result = self.pl_signal_meter_device.vpp()
        vpp = (result[2] * BeastDef.VPP_SCALE_GAIN +
               BeastDef.VPP_SCALE_OFFSET) * self.board_gain
        max_data = (result[0] * BeastDef.SCALE_GAIN +
                    BeastDef.SCALE_OFFSET) * self.board_gain
        min_data = (result[1] * BeastDef.SCALE_GAIN +
                    BeastDef.SCALE_OFFSET) * self.board_gain

        if self.is_use_cal_data():
            if self.board_gain == BeastDef.GAIN1:
                vpp = self.calibrate('VPP_2V', vpp)
            else:
                if self.frequency_measure(
                        BeastDef.DURATION
                )[0] <= BeastDef.BEAST_HIGH_FREQ_THRESHOLD:
                    vpp = self.calibrate('LOW_FREQ_VPP_20V', vpp)
                else:
                    vpp = self.calibrate('HIGH_FREQ_VPP_20V', vpp)

        self.pl_signal_meter_device.close()

        return [vpp, max_data, min_data]

    def rms_measure(self, duration):
        '''
        Measure input signal rms and average voltage in a defined duration.

        Args:
            duration:   int, [1~2000], millisecond time to measure rms.

        Returns:
            list, [value, value], result value contain rms and average voltage, unit is mV.

        Examples:
            # adc_enable() and adc_disable() will operate on Pin
            # which might belongs to another i2c Mux port from on-board eeprom so put outside.
            beast.adc_enable()
            ret = beast.rms_measure(10)
            beast.adc_disable()
            # ret will be list: [rms, average]

        '''
        assert isinstance(duration, int) and duration > 0 and duration <= 2000

        self.pl_signal_meter_device.open()
        self.pl_signal_meter_device.set_vpp_interval(
            BeastDef.BEAST_VPP_DURATION)
        self.pl_signal_meter_device.start_measure(duration,
                                                  BeastDef.BEAST_SAMPLE_RATE,
                                                  self._mask)
        result = self.pl_signal_meter_device.rms()
        rms = (result[0] * BeastDef.RMS_SCALE_GAIN +
               BeastDef.RMS_SCALE_OFFSET) * self.board_gain
        average = (result[1] * BeastDef.SCALE_GAIN +
                   BeastDef.SCALE_OFFSET) * self.board_gain

        if self.is_use_cal_data() and self.signal_type == "AC_VOLT":
            if self.board_gain == BeastDef.GAIN1:
                rms = self.calibrate('RMS_2V', rms)
            else:
                if self.frequency_measure(
                        BeastDef.DURATION
                )[0] <= BeastDef.BEAST_HIGH_FREQ_THRESHOLD:
                    rms = self.calibrate('LOW_FREQ_RMS_20V', rms)
                else:
                    rms = self.calibrate('HIGH_FREQ_RMS_20V', rms)

        self.pl_signal_meter_device.close()

        return [rms, average]

    def level(self):
        '''
        Get current voltage level.

        Returns:
            int, [0, 1], 0 for low level, 1 for high level.

        '''
        return self.pl_signal_meter_device.level()

    def adc_enable(self):
        '''
        This function is used for enable adc output, it is an internal interface function.

        Returns:
            string, "done", api execution successful.

        '''
        self.output_ctrl_pin.set_level(BeastDef.IO_LOW_LEVEL)
        return "done"

    def adc_disable(self):
        '''
        This function is used for disable adc output, it is an internal interface function.

        Returns:
            string, "done", api execution successful.

        '''
        self.output_ctrl_pin.set_level(BeastDef.IO_HIGH_LEVEL)
        return "done"
Esempio n. 9
0
class DarkBeastBase(MIXBoard):
    '''
    Base class of DarkBeast and DarkBeastCompatible.

    Providing common DarkBeast methods

    Args:
        firmware_path:       string,           DarkBeast firmware absolute path
        pl_uart_drv_ko_file: string,           DarkBeast pl_uart_drv.ko drive absolute path
                                               if None creating emulator.
        i2c:             instance(I2C)/None,   Class instance of PLI2CBus, which is used to control cat9555,
                                               eeprom and AD5667 sensor.
        gpio:            instance(GPIO)/None,  Class instance of PLGPIO, UART RX connect AID to enable or disable.
                                               if None creating emulator.
        pull_up_pin:     instance(GPIO)/None,  Class instance of GPIO, data line pull-up control, Provider mode.
                                               if None creating emulator.
        pull_down_pin:   instance(GPIO)/None,  Class instance of GPIO, data line pull-down control, Consumer mode.
                                               if None creating emulator.
        tx_en_pin:       instance(GPIO)/None,  Class instance of GPIO, UART_TX buffer enable control.
                                               if None creating emulator.
        connect_det_pin: instance(GPIO)/None,  Class instance of GPIO, input pin control, detects whether
                                               the DUT is connected. if None creating emulator.
        removal_det_pin: instance(GPIO)/None,  Class instance of GPIO, Input pin control, detect disconnection
                                               from DUT. if None creating emulator.
        a0_pin:          instance(GPIO)/None,  Class instance of Pin, if None creating emulator.
        a1_pin:          instance(GPIO)/None,  Class instance of Pin, if None creating emulator.
        a2_pin:          instance(GPIO)/None,  Class instance of Pin, if None creating emulator.
        elaod_en_pin:    instance(GPIO)/None,  Class instance of Pin, if None creating emulator.
        eeprom_devaddr:  int,                  Eeprom device address.

    '''

    rpc_public_api = [
        'module_init', 'close', 'open', 'communicate', 'aid_connect_set',
        'io_set', 'io_get'
    ] + MIXBoard.rpc_public_api

    def __init__(self, firmware_path, pl_uart_drv_ko_file, i2c, pull_up_pin,
                 pull_down_pin, tx_en_pin, removal_det_pin, connect_det_pin,
                 a0_pin, a1_pin, a2_pin, elaod_en_pin, eeprom_devaddr, gpio):
        self.path = firmware_path
        self.process = None
        self.pl_uart_drv_ko_file = pl_uart_drv_ko_file
        if (firmware_path == '' and pl_uart_drv_ko_file == '' and i2c is None
                and pull_up_pin is None and pull_down_pin is None
                and tx_en_pin is None and removal_det_pin is None
                and connect_det_pin is None):
            self.eeprom = EepromEmulator('eeprom_emulator')
            self.pull_up_pin = GPIOEmulator('pull_up_pin')
            self.pull_down_pin = GPIOEmulator('pull_down_pin')
            self.tx_en_pin = GPIOEmulator('tx_en_pin')
            self.removal_det_pin = GPIOEmulator('removal_det_pin')
            self.connect_det_pin = GPIOEmulator('connect_det_pin')
        elif (firmware_path != '' and pl_uart_drv_ko_file != ''
              and i2c is not None and pull_up_pin is not None
              and pull_down_pin is not None and tx_en_pin is not None
              and removal_det_pin is not None and connect_det_pin is not None):
            self.eeprom = CAT24C02(eeprom_devaddr, i2c)
            self.pull_up_pin = pull_up_pin
            self.pull_down_pin = pull_down_pin
            self.tx_en_pin = tx_en_pin
            self.removal_det_pin = removal_det_pin
            self.connect_det_pin = connect_det_pin
        else:
            raise DarkBeastException(
                '__init__ error! Please check the parameters!')
        self.gpio = gpio
        if (a0_pin is None and a1_pin is None and a2_pin is None
                and elaod_en_pin is None):
            self.a0_pin = Pin(None, DarkBeastDef.A0_PIN_ID)
            self.a1_pin = Pin(None, DarkBeastDef.A1_PIN_ID)
            self.a2_pin = Pin(None, DarkBeastDef.A2_PIN_ID)
            self.elaod_en_pin = Pin(None, DarkBeastDef.ELAOD_EN_PIN_ID)
        elif (a0_pin is not None and a1_pin is not None and a2_pin is not None
              and elaod_en_pin is not None):
            self.a0_pin = a0_pin
            self.a1_pin = a1_pin
            self.a2_pin = a2_pin
            self.elaod_en_pin = elaod_en_pin
        else:
            raise DarkBeastException(
                '__init__ error! Please check the parameters!')

        super(DarkBeastBase, self).__init__(self.eeprom, None)
        self.pin_def = {
            'PULL_UP': self.pull_up_pin,
            'PULL_DOWN': self.pull_down_pin,
            'TX_EN': self.tx_en_pin,
            'REMOVAL_DETECTION': self.removal_det_pin,
            'CONNECT_DETECTION': self.connect_det_pin,
            'A0': self.a0_pin,
            'A1': self.a1_pin,
            'A2': self.a2_pin,
            'ELAOD_EN': self.elaod_en_pin
        }

    def __del__(self):
        self.close()

    def _capture_by_flag(self):
        '''
        capture end flag of result.
        '''
        last_time = time.time()
        data_str = ""
        while (time.time() - last_time < DarkBeastDef.DEFAULT_TIMEOUT):
            read_str = self.process.stdout.read(1)
            data_str = data_str + read_str
            if read_str == DarkBeastDef.RESULT_CHECK_FLAG:
                if data_str.endswith(DarkBeastDef.RESULT_END_FLAG):
                    break
        if time.time() - last_time >= DarkBeastDef.DEFAULT_TIMEOUT:
            raise DarkBeastException('process read wait timeout!')

        return data_str.strip(DarkBeastDef.RESULT_END_FLAG)

    def _update_linux_driver(self):
        ''' update pl_uart_drv.ko if exist '''
        if os.path.isfile(self.pl_uart_drv_ko_file):
            os.system('rmmod ' + self.pl_uart_drv_ko_file)
            os.system('insmod ' + self.pl_uart_drv_ko_file)

    def module_init(self):
        '''
        DarkBeast module init, initialize ad5592r channel mode and open DarkBeast process.

        Returns:
            string, str, return open information.

        Examples:
            DarkBeast.module_init()

        '''
        self._update_linux_driver()
        ret_str = 'done'
        if os.path.isfile(self.path):
            ret_str = self.open()

        # set plgpio init
        if self.gpio:
            self.gpio.set_dir('output')
            self.gpio.set_level(DarkBeastDef.LOWE_LEVEL)

        # set gpio init
        self.pull_up_pin.set_dir('output')
        self.pull_up_pin.set_level(DarkBeastDef.LOWE_LEVEL)
        self.pull_down_pin.set_dir('output')
        self.pull_down_pin.set_level(DarkBeastDef.LOWE_LEVEL)
        self.tx_en_pin.set_dir('output')
        self.tx_en_pin.set_level(DarkBeastDef.LOWE_LEVEL)
        self.connect_det_pin.set_dir('input')
        self.removal_det_pin.set_dir('input')

        # set pin init
        self.a0_pin.set_dir('output')
        self.a0_pin.set_level(DarkBeastDef.LOWE_LEVEL)
        self.a1_pin.set_dir('output')
        self.a1_pin.set_level(DarkBeastDef.LOWE_LEVEL)
        self.a2_pin.set_dir('output')
        self.a2_pin.set_level(DarkBeastDef.LOWE_LEVEL)
        self.elaod_en_pin.set_dir('output')
        self.elaod_en_pin.set_level(DarkBeastDef.LOWE_LEVEL)

        return ret_str

    def close(self):
        '''
        close the DarkBeast process.

        Returns:
            string, "done", api execution successful.

        '''
        if self.process is not None:
            string_list = []
            pid_list = []
            pgid = os.getpgid(self.process.pid)

            command = 'ps -C {}'.format(os.path.basename(self.path))
            progress = Popen(command, shell=True, stdin=PIPE, stdout=PIPE)
            string = progress.communicate()[0]
            string_list = string.split("\n")
            for line in string_list:
                pid = re.findall("\d+", line)
                if len(pid) != 0:
                    pid_list.append(pid[0])

            for pid in pid_list:
                if os.getpgid(int(pid)) == pgid:
                    command = "kill " + pid
                    os.system(command)
            self.process = None

        return 'done'

    def open(self):
        '''
        open DarkBeast process by Popen, and return open information or faild raising Exception

        Returns:
            string, str, return open information.

        '''
        assert os.access(self.path, os.F_OK
                         | os.X_OK), 'path <{}> not exist or execute'.format(
                             self.path)
        if self.process is not None:
            return 'done'

        command = 'cd {};./{}'.format(os.path.dirname(self.path),
                                      os.path.basename(self.path))

        # stderr is standard error output, can be file descriptors and STDOUT. None means no output
        self.process = Popen([command, ""],
                             shell=True,
                             stdin=PIPE,
                             stdout=PIPE,
                             stderr=STDOUT)

        return self._capture_by_flag()

    def communicate(self, command):
        '''
        communicate with DarkBeast process. if process not open, raise DarkBeastException

        Args:
            command:  string, command string.

        Returns:
            string, str, return information.

        Examples:
            cmd_string = "SWITCH2HS"
            print DarkBeast.communicate(cmd_string)

        Raise:
            DarkBeastException:  process not open, communicate error!

        '''
        if self.process is not None:
            self.process.stdin.write(command + "\n")
            self.process.stdin.flush()
            if DarkBeastCMDDef.EXIT == command:
                self.process = None
                return DarkBeastDef.EXIT_STRING
            else:
                return self._capture_by_flag()

        raise DarkBeastException('process not open, communicate error!')

    def aid_connect_set(self, status):
        '''
        AID connect to dut enable or disable

        Args:
            status:  string, ['enable', 'disable'].

        Returns:
            string, "done", api execution successful.

        Examples:
            DarkBeast.aid_connect_set(1)

        '''
        assert status in ('enable', 'disable')
        if status == 'enable':
            level = 1
        else:
            level = 0
        if self.gpio:
            self.gpio.set_level(level)

        return 'done'

    def io_set(self, io_name, level):
        '''
        DarkBeast io control.

        Args:
            io_name:  string, ['PULL_UP', 'PULL_DOWN', 'TX_EN', 'CONNECT_DETECTION', 'REMOVAL_DETECTION',
                               'A0', 'A1', 'A2', 'ELAOD_EN'], io_name can be found in the table below.

                +---------------------+----------------------------------+
                |     io  name        |            io  meaning           |
                +=====================+==================================+
                |  PULL_UP            |  Data line pull-up control,      |
                |                     |  Provider mode.                  |
                +---------------------+----------------------------------+
                |  PULL_DOWN          |  Data line pull-down control,    |
                |                     |  Consumer mode.                  |
                +---------------------+----------------------------------+
                |  TX_EN              |  UART_TX buffer enable control   |
                +---------------------+----------------------------------+
                |  CONNECT_DETECTION  |  input pin control, detect       |
                |                     |  whether the DUT is connected.   |
                +---------------------+----------------------------------+
                |  REMOVAL_DETECTION  |  Input pin control, detect       |
                |                     |  disconnection from DUT.         |
                +--------------------------------------------------------+
                |       A0            |  ORION_COMM_CONN connect         |
                |                     |  PPVBUS_ORION_CONN.              |
                +---------------------+----------------------------------+
                |       A1            |  GND connect ORION_COMM_CONN     |
                +---------------------+----------------------------------+
                |       A2            |  GND connect PPVBUS_ORION_CONN   |
                +---------------------+----------------------------------+
                |       ELAOD_EN      |  A0, A1, A2 control1 disabled or |
                |                     |  enabled  (1/disabled  0/enabled)|
                +---------------------+----------------------------------+

            level:  int, [0, 1], 0/1 means lower/high

        Returns:
            string, "done", api execution successful.

        Examples:
            darkbeast.io_set("PULL_UP", 1)

        '''
        assert io_name in [
            "PULL_UP", "PULL_DOWN", "TX_EN", "CONNECT_DETECTION",
            "REMOVAL_DETECTION", "A0", "A1", "A2", "ELAOD_EN"
        ]

        device = self.pin_def[io_name]
        device.set_level(level)

        return 'done'

    def io_get(self, io_name):
        '''
        DarkBeast io level get

        Args:
            io_name:  string, ['PULL_UP', 'PULL_DOWN', 'TX_EN', 'CONNECT_DETECTION', 'REMOVAL_DETECTION',
                               'A0', 'A1', 'A2', 'ELAOD_EN'], io_name can be found in the table below.

                +---------------------+----------------------------------+
                |     io  name        |            io  meaning           |
                +=====================+==================================+
                |  PULL_UP            |  Data line pull-up control,      |
                |                     |  Provider mode.                  |
                +---------------------+----------------------------------+
                |  PULL_DOWN          |  Data line pull-down control,    |
                |                     |  Consumer mode.                  |
                +---------------------+----------------------------------+
                |  TX_EN              |  UART_TX buffer enable control   |
                +---------------------+----------------------------------+
                |  CONNECT_DETECTION  |  input pin control, detect       |
                |                     |  whether the DUT is connected.   |
                +---------------------+----------------------------------+
                |  REMOVAL_DETECTION  |  Input pin control, detect       |
                |                     |  disconnection from DUT.         |
                +--------------------------------------------------------+
                |       A0            |  ORION_COMM_CONN connect         |
                |                     |  PPVBUS_ORION_CONN.              |
                +---------------------+----------------------------------+
                |       A1            |  GND connect ORION_COMM_CONN     |
                +---------------------+----------------------------------+
                |       A2            |  GND connect PPVBUS_ORION_CONN   |
                +---------------------+----------------------------------+
                |       ELAOD_EN      |  A0, A1, A2 control1 disabled or |
                |                     |  enabled  (1/disabled  0/enabled)|
                +---------------------+----------------------------------+

        Returns:
            int, [0, 1], io level.

        Examples:
            level = darkbeast.io_get("PULL_UP")
            print(level)

        '''
        assert io_name in [
            "PULL_UP", "PULL_DOWN", "TX_EN", "CONNECT_DETECTION",
            "REMOVAL_DETECTION", "A0", "A1", "A2", "ELAOD_EN"
        ]

        device = self.pin_def[io_name]

        return device.get_level()
Esempio n. 10
0
class NegasonicBase(MIXBoard):
    '''
    Base class of Negasonic and NegasonicCompatible,Providing common Negasonic methods.

    Args:
        i2c:              instance(I2C), which is used to access eeprom and nct75, if not given,
                                         will create eeprom and nct75 emulator.
        analyzer:         instance(FFTAnalyzer)/string, if not given, will create emulator.
        signalsource:     instance(MIXSignalSourceSG)/string, if not given, will create emulator.
        adc_rst_pin:      instance(GPIO), used to reset ADC CS5361. Setting low to reset CS5361,
                                          seting high to enable CS5361.
        i2s_rx_en_pin:    instance(GPIO), used to enable fft analyzer module.
        dac_rst_pin:      instance(GPIO), used to reset IIS of the CS4398.
        i2s_tx_en_pin:    instance(GPIO), used to enable signal source module.
        sample_rate:      int, unit Hz, default 192000, used to config the CS5361 or CS4398.
        ipcore:               instance(UART), aggregated MIXAUT1SGR wrapper.
        eeprom_dev_addr:  int,            Eeprom device address.
        sensor_dev_addr:  int,            NCT75 device address.

    '''

    rpc_public_api = [
        'module_init', 'enable_upload', 'disable_upload', 'measure',
        'enable_output', 'disable_output'
    ] + MIXBoard.rpc_public_api

    def __init__(self,
                 i2c,
                 analyzer=None,
                 signal_source=None,
                 adc_rst_pin=None,
                 i2s_rx_en_pin=None,
                 dac_rst_pin=None,
                 i2s_tx_en_pin=None,
                 sample_rate=192000,
                 ipcore=None,
                 eeprom_dev_addr=NegasonicDef.EEPROM_I2C_ADDR,
                 sensor_dev_addr=NegasonicDef.SENSOR_I2C_ADDR,
                 cal_info=None,
                 range_table=None):

        assert sample_rate > 0 and sample_rate <= NegasonicDef.MAX_SAMPLING_RATE
        self.i2c = i2c
        if self.i2c is None:
            self.eeprom = EepromEmulator("cat24cxx_emulator")
            self.nct75 = NCT75Emulator("nct75_emulator")
        else:
            self.eeprom = CAT24C32(eeprom_dev_addr, i2c)
            self.nct75 = NCT75(sensor_dev_addr, i2c)

        super(NegasonicBase, self).__init__(self.eeprom,
                                            self.nct75,
                                            cal_table=cal_info,
                                            range_table=range_table)

        if (ipcore and not analyzer and not signal_source):
            if isinstance(ipcore, basestring):
                axi4 = AXI4LiteBus(ipcore, NegasonicDef.MIXAUT1_REG_SIZE)
                self.ipcore = MIXAUT1SGR(axi4)
            else:
                self.ipcore = ipcore
            self.analyzer = self.ipcore.analyzer
            self.signal_source = self.ipcore.signal_source
            self.adc_rst_pin = adc_rst_pin or Pin(self.ipcore.gpio,
                                                  NegasonicDef.ADC_RESET_PIN)
            self.i2s_rx_en_pin = i2s_rx_en_pin or Pin(
                self.ipcore.gpio, NegasonicDef.I2S_RX_EN_PIN)
            self.dac_rst_pin = dac_rst_pin or Pin(self.ipcore.gpio,
                                                  NegasonicDef.DAC_RESET_PIN)
            self.i2s_tx_en_pin = i2s_tx_en_pin or Pin(
                self.ipcore.gpio, NegasonicDef.I2S_TX_EN_PIN)
        elif (not ipcore and analyzer and signal_source):
            if isinstance(analyzer, basestring):
                axi4 = AXI4LiteBus(analyzer, NegasonicDef.ANALYZER_REG_SIZE)
                analyzer = MIXFftAnalyzerSG(axi4)
            else:
                self.analyzer = analyzer
            if isinstance(signal_source, basestring):
                axi4 = AXI4LiteBus(signal_source,
                                   NegasonicDef.SIGNALSOURCE_REG_SIZE)
                signal_source = MIXSignalSourceSG(axi4)
            else:
                self.signal_source = signal_source
            self.adc_rst_pin = adc_rst_pin
            self.i2s_rx_en_pin = i2s_rx_en_pin
            self.dac_rst_pin = dac_rst_pin
            self.i2s_tx_en_pin = i2s_tx_en_pin
        elif (not ipcore and not analyzer and not signal_source
              and not adc_rst_pin and not i2s_rx_en_pin and not dac_rst_pin
              and not i2s_tx_en_pin):
            self.analyzer = MIXFftAnalyzerSGEmulator(
                "mix_fftanalyzer_sg_emulator")
            self.signal_source = MIXSignalSourceSGEmulator(
                "mix_signalsource_sg_emulator")
            self.adc_rst_pin = Pin(None, NegasonicDef.ADC_RESET_PIN)
            self.i2s_rx_en_pin = Pin(None, NegasonicDef.I2S_RX_EN_PIN)
            self.dac_rst_pin = Pin(None, NegasonicDef.DAC_RESET_PIN)
            self.i2s_tx_en_pin = Pin(None, NegasonicDef.I2S_TX_EN_PIN)
        else:
            if ipcore:
                raise NegasonicException(
                    "parameter 'ipcore' can not be given at same time with 'analyzer', "
                    "'signal_source', 'adc_rst_pin', 'i2s_rx_en_pin', "
                    "'dac_rst_pin', 'i2s_tx_en_pin'")
            else:
                raise NegasonicException(
                    "parameter 'analyzer', 'signal_source', 'adc_rst_pin', "
                    "'i2s_rx_en_pin', 'dac_rst_pin' and 'i2s_tx_en_pin'"
                    " must be given at the same time")

        self.sample_rate = sample_rate

    def __del__(self):
        self.adc_rst_pin.set_level(0)
        self.i2s_rx_en_pin.set_level(0)
        self.dac_rst_pin.set_level(0)
        self.i2s_tx_en_pin.set_level(0)
        self.signal_source.close()

    def module_init(self):
        '''
        Negasonic init module, will reset dac/adc and i2s module.

        Returns:
            string, "done", api execution successful.

        Examples:
            negasonic.module_init()

        '''
        self.adc_rst_pin.set_dir('output')
        self.dac_rst_pin.set_dir('output')
        self.i2s_rx_en_pin.set_dir('output')
        self.i2s_tx_en_pin.set_dir('output')

        # reset ADC
        self.adc_rst_pin.set_level(0)
        time.sleep(NegasonicDef.DELAY_S)
        self.adc_rst_pin.set_level(1)

        # reset DAC
        self.dac_rst_pin.set_level(0)
        time.sleep(NegasonicDef.DELAY_S)
        self.dac_rst_pin.set_level(1)

        self.i2s_rx_en_pin.set_level(1)
        self.load_calibration()

        return "done"

    def enable_upload(self):
        '''
        Negasonic upoad mode open.

        Control audio upload data of ADC when doing measurement. It's not necessary
        enable upload when doing measurement. Note that data transfered into DMA is 32bit each data,
        but high 24bit data is valid. Low 8bit data is invalid. The data format is twos complement.

        Returns:
            string, "done", api execution successful.

        '''
        self.analyzer.enable_upload()
        return "done"

    def disable_upload(self):
        '''
        Negasonic upoad mode close. Close data upload doesn't influence to measure.

        Returns:
            string, "done", api execution successful.

        '''
        self.analyzer.disable_upload()
        return "done"

    def measure(self, bandwidth_hz, harmonic_num, decimation_type=0xFF):
        '''
        Negasonic measure signal's Vpp, RMS, THD+N, THD.

        Args:
            bandwidth_hz:    int, [24~95977], Measure signal's limit bandwidth, unit is Hz. The frequency of the
                                              signal should not be greater than half of the bandwidth.
            harmonic_num:    int, [2~10],     Use for measuring signal's THD.
            decimation_type: int, [1~255],    Decimation for FPGA to get datas. If decimation is 0xFF, FPGA will
                                              choose one suitable number.

        Returns:
            dict, {'vpp': value, 'freq': value, 'thd': value, 'thdn': value, 'rms': value),
                  dict with vpp, freq, thd, thdn, rms.

        Examples:
            result = negasonic.measure(20000, 5, 0xff)
            print result.frequency, result.vpp, result.thdn, result.thd, result.rms

        '''
        assert bandwidth_hz == 'auto' or isinstance(bandwidth_hz, int)
        assert isinstance(harmonic_num, int) and harmonic_num > 0
        assert isinstance(decimation_type, int) and decimation_type > 0

        self.analyzer.disable()
        self.analyzer.enable()
        self.analyzer.analyze_config(self.sample_rate, decimation_type,
                                     bandwidth_hz, harmonic_num)
        self.analyzer.analyze()

        # calculate the actual vpp of HW by VREF
        vpp = self.analyzer.get_vpp() * NegasonicDef.AUDIO_ANALYZER_VREF
        # vpp = RMS * 2 * sqrt(2)
        rms = vpp / NegasonicDef.RMS_TO_VPP_RATIO

        rms = self.calibrate(NegasonicDef.MEASURE_CAL_ITEM, rms)
        vpp = rms * NegasonicDef.RMS_TO_VPP_RATIO

        result = dict()
        result["vpp"] = vpp
        result["freq"] = self.analyzer.get_frequency()
        result["thd"] = self.analyzer.get_thd()
        result["thdn"] = self.analyzer.get_thdn()
        result["rms"] = rms
        return result

    def enable_output(self, freq, rms):
        '''
        Negasonic output sine wave, differencial mode.

        Args:
            freq:    int, [10~50000], unit Hz, Ouput signal's frequency.
            rms:     float, [0~2300], unit mV, Ouput wave's RMS.

        Returns:
            string, "done", api execution successful.

        Examples:
            negasonic.enable_output(10000, 500)

        '''
        assert freq >= NegasonicDef.OUTPUT_FREQ_MIN
        assert freq <= NegasonicDef.OUTPUT_FREQ_MAX
        assert rms >= NegasonicDef.OUTPUT_RMS_MIN
        assert rms <= NegasonicDef.OUTPUT_RMS_MAX

        # enable I2S tx module
        self.i2s_tx_en_pin.set_level(1)

        self.signal_source.open()
        rms = self.calibrate(NegasonicDef.OUTPUT_CAL_ITEM, rms)
        # vpp = RMS * 2 * sqrt(2)
        vpp = rms * NegasonicDef.RMS_TO_VPP_RATIO
        # calculate vpp to vpp scale for FPGA
        vpp_scale = vpp * NegasonicDef.VPP_2_SCALE_RATIO / NegasonicDef.SIGNAL_SOURCE_VREF
        self.signal_source.set_signal_type(NegasonicDef.OUTPUT_WAVE)
        self.signal_source.set_signal_time(NegasonicDef.SIGNAL_ALWAYS_OUTPUT)
        self.signal_source.set_swg_paramter(self.sample_rate, freq, vpp_scale,
                                            NegasonicDef.OUTPUT_SIGNAL_DUTY)
        self.signal_source.output_signal()
        return "done"

    def disable_output(self):
        '''
        Negasonic close sine wave output.

        Returns:
            string, "done", api execution successful.

        Examples:
            negasonic.disable_output()

        '''
        self.signal_source.close()
        self.i2s_tx_en_pin.set_level(0)
        return "done"

    def legacy_write_calibration_cell(self, unit_index, gain, offset,
                                      threshold):
        '''
        MIXBoard calibration data write

        Args:
            unit_index:   int,    calibration unit index.
            gain:         float,  calibration gain.
            offset:       float,  calibration offset.
            threshold:    float,  if value < threshold, use this calibration unit data.

        Returns:
            string, "done", api execution successful.

        Examples:
            board.write_calibration_cel(0, 1.1, 0.1, 100)

        Raise:
            BoardArgCheckError:  unit_index data type is not int type or unit_index < 0.
            calibration unit format:
                Meaning:    Gain,     Offset,   threshold value, Use flag
                Mem size:   4Bytes,   4Bytes,   4Bytes,            Byte
                Data type:  float,    float,    float,            uint8_t
                Formula:    Y = Gain * X  + Offset

        '''
        if not isinstance(unit_index, int) or unit_index < 0:
            raise BoardArgCheckError(
                "calibration unit memory unit_index data type is not int type or unit_index < 0"
            )

        use_flag = self.calibration_info["use_flag"]
        data = (gain, offset, use_flag)
        s = struct.Struct(NegasonicDef.WRITE_CAL_DATA_PACK_FORMAT)
        pack_data = s.pack(*data)

        s = struct.Struct(NegasonicDef.WRITE_CAL_DATA_UNPACK_FORMAT)
        data = s.unpack(pack_data)
        address = self.calibration_info[
            "unit_start_addr"] + NegasonicDef.CAL_DATA_LEN * unit_index
        self.write_eeprom(address, data)
        return "done"

    def legacy_read_calibration_cell(self, unit_index):
        '''
        MIXBoard read calibration data

        Args:
            unit_index: int, calibration unit index.

        Returns:
            dict, {'gain': value, 'offset': value, 'threshold': value, 'is_use': value}.

        Examples:
            data = board.read_calibration_cel(0)
            print(data)

        Raise:
            BoardArgCheckError:  unit_index data type is not int type or unit_index < 0.
            calibration unit format:
                Meaning:    Gain,     Offset,   threshold value, Use flag
                Mem size:   4Bytes,   4Bytes,   4Bytes,            Byte
                Data type:  float,    float,    float,            uint8_t
                Formula:    Y = Gain * X  + Offset

        '''
        if not isinstance(unit_index, int) or unit_index < 0:
            raise BoardArgCheckError(
                "calibration unit memory unit_index data type is not int type or unit_index < 0"
            )

        address = self.calibration_info[
            "unit_start_addr"] + NegasonicDef.CAL_DATA_LEN * unit_index
        data = self.read_eeprom(address, NegasonicDef.READ_CAL_BYTE)

        s = struct.Struct(NegasonicDef.READ_CAL_DATA_PACK_FORMAT)
        pack_data = s.pack(*data)

        s = struct.Struct(NegasonicDef.READ_CAL_DATA_UNPACK_FORMAT)
        result = s.unpack(pack_data)

        threshold = 0.0
        for cal_item in negasonic_calibration_info:
            for level in negasonic_calibration_info[cal_item]:
                if unit_index == negasonic_calibration_info[cal_item][level][
                        "unit_index"]:
                    threshold = negasonic_calibration_info[cal_item][level][
                        "limit"][0]

        if self.calibration_info["use_flag"] != result[2]:
            return {"gain": 1.0, "offset": 0.0, "threshold": 0, "is_use": True}
        else:
            return {
                "gain": result[0],
                "offset": result[1],
                "threshold": threshold,
                "is_use": True
            }

    def legacy_erase_calibration_cell(self, unit_index):
        '''
        MIXBoard erase calibration unit

        Returns:
            string, "done", api execution successful.

        Examples:
            board.erase_calibration_cell(0)

        '''
        if not isinstance(unit_index, int) or unit_index < 0:
            raise BoardArgCheckError(
                "calibration unit memory unit_index data type is not int type or unit_index < 0"
            )

        data = [0xff for i in range(9)]
        address = self.calibration_info["unit_start_addr"] + 9 * unit_index
        self.write_eeprom(address, data)
        return "done"
Esempio n. 11
0
class KarmaBase(SGModuleDriver):
    '''
    Base class of Karma and KarmaCompatible.

    Providing common Karma methods.

    Args:
        i2c:        instance(I2C), instance of I2CBus, which is used to control cat24c32 and nct75.
        ipcore:     instance(MIXBT001SGR)/string, If given, then use MIXBT001SGR's AD7175, MIXQSPI, MIXGPIO.
        eeprom_dev_addr:    int, eeprom device address.
        sensor_dev_addr:    int, NCT75 device address.
        range_table:        dict, which is ICI calibration range table.

    '''

    rpc_public_api = [
        'dds_reset', 'dds_control', 'discharge_control', 'set_line_path',
        'get_line_path', 'get_offset_cal_data', 'sine_output',
        'resistance_measure', 'read_adc_voltage', 'set_sampling_rate',
        'get_sampling_rate'
    ] + SGModuleDriver.rpc_public_api

    def __init__(self,
                 i2c,
                 ipcore,
                 eeprom_dev_addr=KarmaDef.EEPROM_DEV_ADDR,
                 sensor_dev_addr=KarmaDef.SENSOR_DEV_ADDR,
                 range_table=karma_range_table):

        if (i2c and ipcore):
            if isinstance(ipcore, basestring):
                axi4_bus = AXI4LiteBus(ipcore, KarmaDef.REG_SIZE)
                self.ipcore = MIXBT001SGR(axi4_bus=axi4_bus,
                                          ad717x_chip='AD7175',
                                          ad717x_mvref=KarmaDef.ADC_VREF,
                                          use_spi=True,
                                          use_gpio=True)
            else:
                self.ipcore = ipcore

            self.ad7175 = self.ipcore.ad717x
            self.ad7175.config = {
                'ch0': {
                    'P': 'AIN0',
                    'N': 'AIN1'
                },
                'ch1': {
                    'P': 'AIN2',
                    'N': 'AIN3'
                }
            }
            self.vx_ix_select_pin = Pin(self.ipcore.gpio,
                                        KarmaDef.VX_IX_SELECT_PIN)
            self.cur_set1_pin = Pin(self.ipcore.gpio, KarmaDef.CUR_SET1_PIN)
            self.cur_set2_pin = Pin(self.ipcore.gpio, KarmaDef.CUR_SET2_PIN)
            self.discharge_pin = Pin(self.ipcore.gpio, KarmaDef.DISCHARGE_PIN)
            self.lpf_select_pin = Pin(self.ipcore.gpio,
                                      KarmaDef.LPF_SELECT_PIN)
            self.reset_l_pin = Pin(self.ipcore.gpio, KarmaDef.RESET_L_PIN)
            self.trigger_l_pin = Pin(self.ipcore.gpio, KarmaDef.TRIGGER_L_PIN)
            self.ad9106 = AD9106(self.ipcore.spi, KarmaDef.AD9106_MCLK,
                                 KarmaDef.AD9106_DEFAULT_VREF)
            self.eeprom = CAT24C32(eeprom_dev_addr, i2c)
            self.sensor = NCT75(sensor_dev_addr, i2c)
            self.line_path = ""
            self.sine_data_length = 0
            self.cosine_data_length = 0
            self.cycle_count = 2
            super(KarmaBase, self).__init__(self.eeprom,
                                            self.sensor,
                                            range_table=range_table)

        else:
            raise KarmaException('Parameter error')

    def post_power_on_init(self, timeout=KarmaDef.DEFAULT_TIMEOUT):
        '''
        Init Karma module to a know harware state.

        Args:
            timeout:      float, (>=0), default 6, unit Second, execute timeout.

        '''
        self.reset(timeout)

    def pre_power_down(self, timeout=KarmaDef.DEFAULT_TIMEOUT):
        '''
        Put the hardware in a safe state to be powered down.

        Args:
            timeout:      float, (>=0), default 6, unit Second, execute timeout.
        '''
        start_time = time.time()

        while True:
            try:
                self.discharge_control('open', KarmaDef.DISCHARGE_DEFAULT_TIME)
                self.discharge_control('close')
                self.dds_reset()
                self.dds_control('close')
                return
            except Exception as e:
                if time.time() - start_time > timeout:
                    raise KarmaException("Timeout: {}".format(e.message))

    def reset(self, timeout=KarmaDef.DEFAULT_TIMEOUT):
        '''
        Reset the instrument module to a know hardware state.

        Args:
            timeout:      float, (>=0), default 6, unit Second, execute timeout.

        '''
        start_time = time.time()

        while True:
            try:
                self.vx_ix_select_pin.set_dir(KarmaDef.IO_OUTPUT_DIR)
                self.cur_set1_pin.set_dir(KarmaDef.IO_OUTPUT_DIR)
                self.cur_set2_pin.set_dir(KarmaDef.IO_OUTPUT_DIR)
                self.discharge_pin.set_dir(KarmaDef.IO_OUTPUT_DIR)
                self.lpf_select_pin.set_dir(KarmaDef.IO_OUTPUT_DIR)
                self.reset_l_pin.set_dir(KarmaDef.IO_OUTPUT_DIR)
                self.trigger_l_pin.set_dir(KarmaDef.IO_OUTPUT_DIR)
                self.discharge_control('open', KarmaDef.DISCHARGE_DEFAULT_TIME)
                self.discharge_control('close')
                self.dds_reset()
                self.dds_control('close')
                self.ad9106.set_ref_voltage(KarmaDef.AD9106_DEFAULT_VREF)
                self.ad7175.channel_init()
                self.set_sampling_rate(0, KarmaDef.ADC_DEFAULT_SAMPLE_RATE)
                self.set_sampling_rate(1, KarmaDef.ADC_DEFAULT_SAMPLE_RATE)
                self.write_sine_pattern(1000, KarmaDef.IOUT_DEFAULT_VPP,
                                        self.cycle_count)
                self.write_cosine_pattern(1000, KarmaDef.IOUT_DEFAULT_VPP,
                                          self.cycle_count)
                return
            except Exception as e:
                if time.time() - start_time > timeout:
                    raise KarmaException("Timeout: {}".format(e.message))

    def get_driver_version(self):
        '''
        Get driver version.

        Returns:
            string, current driver version.
        '''
        return __version__

    def dds_reset(self):
        '''
        Karma reset ad9106. This is private function.

        Returns:
            string, "done", api execution successful.

        Examples:
            karma.dds_reset()

        '''
        self.reset_l_pin.set_level(0)
        time.sleep(KarmaDef.RST_DELAY_MS / 1000.0)
        self.reset_l_pin.set_level(1)

        return "done"

    def dds_control(self, mode):
        '''
        Pattern trigger input of ad9106. This is private function.

        Args:
            mode:    string, ['open', 'close'], 'open' mean enable pattern trigger input of ad9106,
                                                'close' mean disable pattern trigger input of ad9106.

        Returns:
            string, "done", api execution successful.

        Examples:
            karma.dds_control('open')

        '''
        assert mode in ['open', 'close']

        if mode == 'open':
            self.trigger_l_pin.set_level(0)
        else:
            self.trigger_l_pin.set_level(1)

        return "done"

    def set_sampling_rate(self, channel, sampling_rate):
        '''
        Karma set sampling rate. This is private function.

        Args:
            channel:           int, [0, 1], channel 0 is for the real part of Vx or Ix,
                                            channel 1 is for the imaginary part of Vx or Ix.
            sampling_rate:     float, [5~250000], adc measure sampling rate, which not continuouse,
                                                  please refer ad7175 datasheet.

        Returns:
            string, "done", api execution successful.

        Examples:
           karma.set_sampling_rate(1, 10000)

        '''
        assert channel in KarmaDef.ADC_CHANNEL_LIST
        assert 5 <= sampling_rate <= 250000

        if sampling_rate != self.get_sampling_rate(channel):
            self.ad7175.set_sampling_rate(channel, sampling_rate)

        return "done"

    def get_sampling_rate(self, channel):
        '''
        Karma get sampling rate of adc. This is private function.

        Args:
            channel:     int, [0, 1], channel 0 is for the real part of Vx or Ix,
                                      channel 1 is for the imaginary part of Vx or Ix.

        Returns:
            string, "done", api execution successful.

        Examples:
            sampling_rate = karma.get_sampling_rate(1)
            print(sampling_rate)

        '''
        assert channel in KarmaDef.ADC_CHANNEL_LIST

        return self.ad7175.get_sampling_rate(channel)

    def discharge_control(self,
                          mode,
                          discharge_time=KarmaDef.CLOSE_DEFAULT_TIME):
        '''
        Discharge control. This is private function.

        Args:
            mode:    string, ['open', 'close'], 'open' mean discharge,
                                                     'close' mean charge.
            discharge_time:    float, (>=0), default 0, unit ms, discharge time.

        Returns:
            string, "done", api execution successful.

        Examples:
            karma.discharge_control('open', 20)

        '''
        assert mode in ['open', 'close']

        bits = karma_line_info[mode]['bits']
        for bit in bits:
            if isinstance(bit, list):
                for io_conf in bit:
                    eval(io_conf[0]).set_level(io_conf[1])
                time.sleep(KarmaDef.RST_DELAY_MS / 1000.0)
            else:
                eval(bit[0]).set_level(bit[1])
        time.sleep(discharge_time / 1000.0)
        self.line_path = mode

        return "done"

    def set_line_path(self,
                      channel,
                      scope,
                      freq,
                      delay_time=KarmaDef.RELAY_DELAY_MS):
        '''
        Karma set channel path. This is debug function.

        Args:
            channel:     string, ['Vx', 'Ix'], channel 'Vx' mean the vector voltage of the real part or
                                               the imaginary part of impedance,
                                               channel 'Ix' mean the vector current of the real part or
                                               the imaginary part of impedance.
            scope:       string, ['3000mohm', '300mohm'], impedance measurement range.
            freq:        string, ['1kHz', '1Hz'], impedance measurement frequency.
            delay_time:  int, (>0), default 5, unit ms.

        Returns:
            string, "done", api execution successful.

        Examples:
            karma.set_line_path('Vx', '3000mohm', '1kHz')

        '''
        assert channel in KarmaDef.CHANNEL_LIST
        assert scope in KarmaDef.RANGE_LIST
        assert freq in KarmaDef.FREQ_LIST

        line_path = freq + '_' + scope + '_' + channel
        if line_path != self.line_path:
            bits = karma_line_info[channel]['bits']
            for bit in bits:
                eval(bit[0]).set_level(bit[1])

            bits = karma_line_info[freq]['bits']
            for bit in bits:
                eval(bit[0]).set_level(bit[1])

            bits = karma_line_info[scope]['bits']
            for bit in bits:
                eval(bit[0]).set_level(bit[1])
                time.sleep(delay_time / 1000.0)

            self.line_path = line_path

        return "done"

    def get_line_path(self):
        '''
        Karma get channel path.

        Returns:
            string, value is channel path.

        Examples:
            path = karma.get_line_path()
            print(path)

        '''
        return self.line_path

    def sine_output(self, dac_channel, freq, vpp, offset=0, phase_value=0):
        '''
        AD9106 output sine waveform, this is a private function.

        Args:
            dac_channel:     int, [1, 2, 3], channel for dds output.
            freq:            int, unit Hz, frequency of wavefrom.
            vpp:             float, unit mVpp, vpp of waveform.
            offset:          float, unit mV, default 0, offset of waveform.
            phase_value:     float, (>=0), default 0, phase of waveform.

        Returns:
            string, "done", api execution successful.

        Examples:
            karma.sine_output(0, 1000, 500)

        '''
        assert dac_channel in [1, 2, 3]
        assert 0 <= phase_value

        # IOUTFSx = 32 * VREFIO / xRSET = 32 * VREFIO / 8060, VOUTFS = IOUTFS x Rl = IOUTFS * 249
        vp = 32 * KarmaDef.AD9106_DEFAULT_VREF * 249 / 8060.0
        gain = vpp / (vp * 2)
        self.ad9106.set_phase(dac_channel, phase_value)
        self.ad9106.sine_output(dac_channel, freq, gain, offset)
        return "done"

    def write_sine_pattern(self, freq, vpp, cycle_count=1):
        '''
        Write sine wave data to SRAM.

        Args:
            freq: int/float, unit Hz, frequency value.
            vpp: int/float, unit mVpp, vpp value.
            cycle_count, int/float, (>0), default 1, the number of waveform cycles in SRAM.

        Returns:
            string, "done", api execution successful.

        Examples:
            karma.write_sine_pattern(1000, 1977, 4)

        '''
        sine_data_list = self.calculate_sine_data(freq, vpp)
        sine_data_list = sine_data_list * cycle_count
        if len(sine_data_list) > 4096:
            raise KarmaException("Out of range of per pattern_period")
        self.ad9106.write_pattern(KarmaDef.AD9106_SRAM_SINE_ADDR,
                                  sine_data_list)
        self.sine_data_length = len(sine_data_list)

        return "done"

    def calculate_sine_data(self, freq, vpp):
        '''
        Calculate sine data.

        Args:
            freq: int/float, unit Hz, frequency value.
            vpp: int/float, unit mVpp, vpp value.

        Returns:
            list, sine wave data.

        Examples:
            karma.calculate_sine_data(1000, 1977)

        '''

        data_list = list()
        vp = vpp / 2
        freq = freq * 1.0
        total_points = int(KarmaDef.AD9106_SAMPLE_RATE / freq)
        for i in range(total_points):
            point_vp = math.sin(2 * math.pi * (i / (total_points * 1.0))) * vp
            if (point_vp < 0):
                if point_vp < -KarmaDef.AD9106_DEFAULT_VREF:
                    point_vp = -KarmaDef.AD9106_DEFAULT_VREF
                result = int(
                    KarmaDef.calculation_formula['calculate_negative_data']
                    (point_vp)) & 0xfff
            else:
                if point_vp > KarmaDef.AD9106_DEFAULT_VREF:
                    point_vp = KarmaDef.AD9106_DEFAULT_VREF
                result = int(
                    KarmaDef.calculation_formula['calculate_positive_data']
                    (point_vp)) & 0xfff
            data_list.append(result)

        return data_list

    def write_cosine_pattern(self, freq, vpp, cycle_count=1):
        '''
        Write cosine wave data to SRAM.

        Args:
            freq: int/float, unit Hz, frequency value.
            vpp: int/float, unit mVpp, vpp value.
            cycle_count, int/float, (>0), default 1, the number of waveform cycles in SRAM.

        Returns:
            string, "done", api execution successful.

        Examples:
            karma.write_cosine_pattern(1000, 1977, 4)

        '''
        cosine_data_list = self.calculate_cosine_data(freq, vpp)
        cosine_data_list = cosine_data_list * cycle_count
        if len(cosine_data_list) > 4096:
            raise KarmaException("Out of range of per pattern_period")
        self.ad9106.write_pattern(KarmaDef.AD9106_SRAM_COSINE_ADDR,
                                  cosine_data_list)
        self.cosine_data_length = len(cosine_data_list)

        return "done"

    def calculate_cosine_data(self, freq, vpp):
        '''
        Calculate cosine data.

        Args:
            freq: int/float, unit Hz, frequency value.
            vpp: int/float, unit mVpp, vpp value.

        Returns:
            list, cosine wave data.

        Examples:
            karma.calculate_cosine_data(1000, 1977)

        '''

        data_list = list()
        vp = vpp / 2
        freq = freq * 1.0
        total_points = int(KarmaDef.AD9106_SAMPLE_RATE / freq)
        for i in range(total_points):
            point_vp = math.cos(2 * math.pi * (i / (total_points * 1.0))) * vp
            if (point_vp < 0):
                if point_vp < -KarmaDef.AD9106_DEFAULT_VREF:
                    point_vp = -KarmaDef.AD9106_DEFAULT_VREF
                result = int(
                    KarmaDef.calculation_formula['calculate_negative_data']
                    (point_vp)) & 0xfff
            else:
                if point_vp > KarmaDef.AD9106_DEFAULT_VREF:
                    point_vp = KarmaDef.AD9106_DEFAULT_VREF
                result = int(
                    KarmaDef.calculation_formula['calculate_positive_data']
                    (point_vp)) & 0xfff
            data_list.append(result)

        return data_list

    def output_sine_pattern(self,
                            freq,
                            vpp,
                            cycle_count,
                            pat_period_base=1,
                            dac_repeat_cycle=255):
        '''
        Output sine wave in pattern period.

        Args:
            freq: int/float, unit Hz, frequency value, it is related to the clock of DAC.
            vpp:    float, unit mVpp, vpp of waveform.
            cycle_count: int/float, (>0), the number of waveform cycles in per pattern_period.
            pat_period_base: int, [0-0xf], default 1, the number of dac clock periods per pattern_period lsb.
            dac_repeat_cycle:    int, [0-255], default 1, Number of DAC pattern repeat cycles + 1,
                                                (0 means repeat 1 pattern).

        Returns:
            string, "done", api execution successful.

        Examples:
            karma.write_sine_pattern(1000, 1977, 4)
            karma.output_sine_pattern(1000, 1977, 4)
        '''
        self.dds_control('close')
        # IOUTFSx = 32 * VREFIO / xRSET = 32 * VREFIO / 8060, VOUTFS = IOUTFS x Rl = IOUTFS * 249
        vp = 32 * KarmaDef.AD9106_DEFAULT_VREF * 249 / 8060.0
        gain = vpp / (vp * 2)
        self.ad9106.play_pattern(1,
                                 freq,
                                 KarmaDef.AD9106_SRAM_SINE_START_ADDR,
                                 self.sine_data_length - 1,
                                 pat_period_base=pat_period_base,
                                 dac_repeat_cycle=dac_repeat_cycle,
                                 cycle_value=cycle_count,
                                 gain=gain)
        self.ad9106.play_pattern(2,
                                 freq,
                                 KarmaDef.AD9106_SRAM_COSINE_START_ADDR,
                                 self.cosine_data_length - 1,
                                 pat_period_base=pat_period_base,
                                 dac_repeat_cycle=dac_repeat_cycle,
                                 cycle_value=cycle_count,
                                 gain=gain)
        self.ad9106.play_pattern(3,
                                 freq,
                                 KarmaDef.AD9106_SRAM_SINE_START_ADDR,
                                 self.sine_data_length - 1,
                                 pat_period_base=pat_period_base,
                                 dac_repeat_cycle=dac_repeat_cycle,
                                 cycle_value=cycle_count,
                                 gain=gain)
        self.ad9106.set_ram_update()
        self.dds_control('open')

        return "done"

    def resistance_measure(self,
                           scope,
                           freq,
                           discharge_time=KarmaDef.DISCHARGE_DEFAULT_TIME,
                           delay_time=KarmaDef.RELAY_DELAY_MS):
        '''
        Karma measure impedance.

        Please notes that the hardware does not support 1Hz measurement temporarily.

        Args:
            scope:    string, ['3000mohm', '300mohm'], impedance measurement range.
            freq:     string, ['1kHz', '1Hz'], impedance measurement frequency,
                                            the hardware does not support '1Hz' measurement temporarily.
            discharge_time:    float, (>=20), default 20, unit ms, discharge time.
            delay_time:    int, (>0), default 5, unit ms.

        Returns:
            dict, {'Rs': (Rs, 'ohm'), 'Xs': (Xs, 'ohm')}, for the real part and imaginary part of impedance.

        Examples:
            result = karma.resistance_measure('3000mohm', '1kHz', 20)
            print(result)

        '''
        assert scope in KarmaDef.RANGE_LIST
        assert freq in KarmaDef.FREQ_LIST
        assert 20 <= discharge_time

        sine_freq_dict = {'1kHz': 1000, '1Hz': 1}
        sine_freq = sine_freq_dict[freq]
        r_sense_dict = {'3000mohm': 200, '300mohm': 20}
        r_sense = r_sense_dict[scope]
        self.discharge_control('close')
        if freq == '1kHz':
            self.output_sine_pattern(sine_freq, KarmaDef.IOUT_DEFAULT_VPP,
                                     self.cycle_count)
        else:
            raise KarmaException(
                "Hardware does not support 1Hz measurement temporarily")
        self.set_line_path('Ix', scope, freq, delay_time)
        u_re = self.ad7175.read_volt(0)
        u_im = self.ad7175.read_volt(1)
        u_re = self.calibrate(scope + '_Ix_ch1', u_re, sine_freq)
        u_im = self.calibrate(scope + '_Ix_ch2', u_im, sine_freq)
        self.set_line_path('Vx', scope, freq, delay_time)
        self.output_sine_pattern(sine_freq, KarmaDef.IOUT_DEFAULT_VPP,
                                 self.cycle_count)
        v_re = self.ad7175.read_volt(0)
        v_im = self.ad7175.read_volt(1)
        v_re = self.calibrate(scope + '_Vx_ch1', v_re, sine_freq)
        v_im = self.calibrate(scope + '_Vx_ch2', v_im, sine_freq)
        self.discharge_control('open', discharge_time)
        self.discharge_control('close')

        result = dict()
        unit = 'ohm'
        r_s = -(((v_re * u_re) + (v_im * u_im)) * r_sense /
                ((u_re**2 + u_im**2) * 65.0))
        x_s = -(((v_im * u_re) - (v_re * u_im)) * r_sense /
                ((u_re**2 + u_im**2) * 65.0))
        r_s = self.calibrate(scope, r_s)
        result['Rs'] = (r_s, unit)
        result['Xs'] = (x_s, unit)

        return result

    def read_adc_voltage(self, channel):
        '''
        Read adc voltage, this is a debug function.

        Args:
            channel:    int, [0, 1]. adc channel.

        Returns:
            float, unit is mV.

        Examples:
            result = karma.read_adc_voltage(0)
            print(result)

        '''
        assert channel in KarmaDef.ADC_CHANNEL_LIST
        return self.ad7175.read_volt(channel)

    def get_offset_cal_data(self, frequency):
        '''
        Get offset calibration data, this is a debug function.

        Args:
            frequency:    string, ['1kHz', '1Hz'], frequency.

        Returns:
            dict,
            e.g. {'1kHz_300mohm_Vx_ch1': 37.65133247681454,
                  '1Hz_300mohm_Ix_ch1': 13.997853636613705,
                  '1kHz_300mohm_Ix_ch2': 43.30072065000062,
                  '1kHz_300mohm_Vx_ch2': 43.32542677673262,
                  '1Hz_3000mohm_Ix_ch2': 12.980402289652961,
                  '1kHz_300mohm_Ix_ch1': 37.70277128832169,
                  '1Hz_300mohm_Ix_ch2': 13.53880843751481,
                  '1Hz_3000mohm_Ix_ch1': 13.895274036841037,
                  '1Hz_300mohm_Vx_ch2': 20.317376870952657,
                  '1Hz_3000mohm_Vx_ch2': 28.178902159863846,
                  '1kHz_3000mohm_Vx_ch1': 37.72822247315779,
                  '1kHz_3000mohm_Vx_ch2': 43.30441613819696,
                  '1Hz_3000mohm_Vx_ch1': 27.222545577439405,
                  '1kHz_3000mohm_Ix_ch2': 43.30870767287659,
                  '1Hz_300mohm_Vx_ch1': 27.315230805589607,
                  '1kHz_3000mohm_Ix_ch1': 37.71659956673381}.

        Examples:
            result = karma.get_offset_cal_data()
            print(result)

        '''
        assert frequency in KarmaDef.FREQ_LIST

        cal_offset = OrderedDict()
        adc_channel_dict = {'ch1': 0, 'ch2': 1}
        sine_freq_dict = {'1kHz': 1000, '1Hz': 1}
        if frequency == '1Hz':
            CAL_OFFSET_RANGE = CAL_OFFSET_RANGE_1Hz
        else:
            CAL_OFFSET_RANGE = CAL_OFFSET_RANGE_1kHz

        for offset_range in CAL_OFFSET_RANGE:
            chan = offset_range.split('_')
            freq = chan[0]
            scope = chan[1]
            vi_chan = chan[2]
            adc_chan = chan[3]
            adc_channel = adc_channel_dict[adc_chan]
            sine_freq = sine_freq_dict[freq]
            self.discharge_control('close')
            self.sine_output(1, sine_freq, KarmaDef.IOUT_DEFAULT_VPP)
            self.sine_output(2,
                             sine_freq,
                             KarmaDef.IOUT_DEFAULT_VPP,
                             phase_value=KarmaDef.COSINE_PHASE)
            self.dds_control('open')
            self.set_line_path(vi_chan, scope, freq)

            data_list = []
            for i in range(5):
                data_list.append(self.read_adc_voltage(adc_channel))

            self.dds_control('close')
            self.discharge_control('open', discharge_time=20)
            self.discharge_control('close')

            data_list.sort()
            data_list = data_list[1:-1]
            avg_data = sum(data_list) / len(data_list)

            path = freq + '_' + scope + '_' + vi_chan + '_' + adc_chan
            cal_offset[path] = avg_data

        return cal_offset

    def calibrate(self, range_name, data, frequency=None):
        '''
        This function is used to calibrate data.

        Args:
            range_name:     string, which range used to do calibration
            data:           float, raw data which need to be calibrated.
            frequency:      int/None, default None.

        Returns:
            float:          calibrated data.

        Examples:
            result = karma.calibrate('300mohm_Ix_ch1', 100, 1000)
            print(result)
        '''
        if frequency:

            if self._cal_common_error is not None:
                raise self._cal_common_error

            assert range_name in self._calibration_table

            items = self._calibration_table[range_name]
            if len(items) == 0:
                return data

            if range_name in self._range_err_table:
                raise self._range_err_table[range_name]

            level = 0
            for i in range(len(items)):
                if frequency <= items[i]['threshold']:
                    level = i
                    break
                if not items[i]['is_use']:
                    break
                level = i
            return items[level]['gain'] * data + items[level]['offset']
        else:
            return super(KarmaBase, self).calibrate(range_name, data)
Esempio n. 12
0
class Audio005Base(SGModuleDriver):
    '''
    Audio005Base is a high resolution differential input/output digital audio module.

    Args:
        i2c:    instance(I2C), the instance of I2C bus. which will be used to used
                               to control eeprom, sensor and io expander.
        ipcore: instance(MIXAudio005SGR), the instance of MIXAudio005SGR, which include
                                    AD717x, FFT Analyzer, Signal Source and gpio
                                    function. If device name string is passed
                                    to the parameter, the ipcore can be instanced
                                    in the module.

    '''
    rpc_public_api = [
        'enable_upload', 'disable_upload', 'measure', 'enable_output',
        'disable_output'
    ] + SGModuleDriver.rpc_public_api

    def __init__(self, i2c, ipcore, range_table=starlordii_range_table):

        self.eeprom = CAT24C32(Audio005Def.EEPROM_I2C_ADDR, i2c)
        self.nct75 = NCT75(Audio005Def.TEMP_I2C_ADDR, i2c)
        self.pca9536 = PCA9536(Audio005Def.PCA9536_DEV_ADDR, i2c)

        if isinstance(ipcore, basestring):
            ipcore = MIXAudio005SGR(ipcore)

        self.ipcore = ipcore
        self.analyzer = self.ipcore.analyzer
        self.signal_source = self.ipcore.signal_source
        self.adc_rst_pin = Pin(self.ipcore.gpio, Audio005Def.ADC_RESET_PIN)
        self.i2s_rx_en_pin = Pin(self.ipcore.gpio, Audio005Def.I2S_RX_EN_PIN)
        self.dac_rst_pin = Pin(self.ipcore.gpio, Audio005Def.DAC_RESET_PIN)
        self.i2s_tx_en_pin = Pin(self.ipcore.gpio, Audio005Def.I2S_TX_EN_PIN)
        self.i2s_ch_select = [
            Pin(self.ipcore.gpio, Audio005Def.I2S_CH_SELECT_2),
            Pin(self.ipcore.gpio, Audio005Def.I2S_CH_SELECT_3)
        ]

        super(Audio005Base, self).__init__(self.eeprom,
                                           self.nct75,
                                           range_table=range_table)
        self.is_enable_upload = False

    def post_power_on_init(self, timeout=Audio005Def.TIME_OUT):
        '''
        Init audio005 module to a know harware state.

        This function will reset reset dac/adc and i2s module.

        Args:
            timeout:      float, (>=0), default 1, unit Second, execute timeout.
        '''
        self.reset(timeout)

    def reset(self, timeout=Audio005Def.TIME_OUT):
        '''
        Reset the instrument module to a know hardware state.

        Args:
            timeout:      float, (>=0), default 1, unit Second, execute timeout.
        '''
        start_time = time.time()
        while True:
            try:
                self.adc_rst_pin.set_dir(Audio005Def.IO_DIR_OUTPUT)
                self.dac_rst_pin.set_dir(Audio005Def.IO_DIR_OUTPUT)
                self.i2s_rx_en_pin.set_dir(Audio005Def.IO_DIR_OUTPUT)
                self.i2s_tx_en_pin.set_dir(Audio005Def.IO_DIR_OUTPUT)

                # reset ADC
                self.adc_rst_pin.set_level(Audio005Def.PIN_LEVEL_LOW)
                time.sleep(Audio005Def.RELAY_DELAY_S)
                self.adc_rst_pin.set_level(Audio005Def.PIN_LEVEL_HIGH)

                # reset DAC
                self.dac_rst_pin.set_level(Audio005Def.PIN_LEVEL_LOW)
                time.sleep(Audio005Def.RELAY_DELAY_S)
                self.dac_rst_pin.set_level(Audio005Def.PIN_LEVEL_HIGH)

                # reset i2s rx
                self.i2s_rx_en_pin.set_level(Audio005Def.PIN_LEVEL_LOW)

                # reset i2s tx
                self.i2s_tx_en_pin.set_level(Audio005Def.PIN_LEVEL_LOW)

                # io init
                self.pca9536.set_pin_dir(Audio005Def.ADC_CH_RIGHT_CTL_BIT,
                                         Audio005Def.IO_DIR_OUTPUT)
                self.pca9536.set_pin_dir(Audio005Def.ADC_CH_LEFT_CTL_BIT,
                                         Audio005Def.IO_DIR_OUTPUT)
                self.pca9536.set_pin_dir(Audio005Def.ADCM0_CTL_BIT,
                                         Audio005Def.IO_DIR_OUTPUT)
                self.pca9536.set_pin_dir(Audio005Def.ADCM1_CTL_BIT,
                                         Audio005Def.IO_DIR_OUTPUT)

                self.pca9536.set_pin(Audio005Def.ADC_CH_RIGHT_CTL_BIT,
                                     Audio005Def.PIN_LEVEL_LOW)
                self.pca9536.set_pin(Audio005Def.ADC_CH_LEFT_CTL_BIT,
                                     Audio005Def.PIN_LEVEL_LOW)
                self.pca9536.set_pin(Audio005Def.ADCM0_CTL_BIT,
                                     Audio005Def.PIN_LEVEL_LOW)
                self.pca9536.set_pin(Audio005Def.ADCM1_CTL_BIT,
                                     Audio005Def.PIN_LEVEL_LOW)
                return
            except Exception as e:
                if time.time() - start_time > timeout:
                    raise Audio005Exception("Timeout: {}".format(e.message))

    def pre_power_down(self, timeout=Audio005Def.TIME_OUT):
        '''
        Put the hardware in a safe state to be powered down.

        This function will set pca9536 io direction to output and set pin level to 0.

        Args:
            timeout:      float, (>=0), default 1, unit Second, execute timeout.
        '''
        start_time = time.time()
        while True:
            try:
                self.adc_rst_pin.set_level(0)
                self.i2s_rx_en_pin.set_level(0)
                self.dac_rst_pin.set_level(0)
                self.i2s_tx_en_pin.set_level(0)
                self.signal_source.close()
                return
            except Exception as e:
                if time.time() - start_time > timeout:
                    raise Audio005Exception("Timeout: {}".format(e.message))

    def get_driver_version(self):
        '''
        Get audio005 driver version.

        Returns:
            string, current driver version.
        '''
        return __version__

    def enable_upload(self):
        '''
        Enable module data upload.

        Returns:
            string, "done", execution successful.
        '''
        self.i2s_rx_en_pin.set_level(Audio005Def.I2S_RX_ENABLE)
        self.analyzer.enable_upload()
        self.is_enable_upload = True

        return "done"

    def disable_upload(self):
        '''
        Disable module data upload.

        Returns:
            string, "done", execution successful.
        '''
        self.analyzer.disable_upload()
        self.i2s_rx_en_pin.set_level(Audio005Def.I2S_RX_DISABLE)
        self.is_enable_upload = False

        return "done"

    def measure(self,
                channel,
                scope,
                bandwidth_hz,
                harmonic_count,
                decimation_type=0xFF,
                sampling_rate=Audio005Def.DEF_SAMPLING_RATE):
        '''
        Measure audio input signal, which captures data using CS5361.

        Args:
            channel:         string, ['left', 'right'], select input signal channel.
            scope:           string, ['2V', '20mV'], AD7175 measurement range.
            bandwidth_hz:    int/string, [42~48000], unit Hz, the signal bandwidth.
                             In theory the bandwidth must smaller than half the sampling rate.
                             eg, if sampling_rate = 192000, so bandwidth_hz  < 96000.
                             The bandwidth must be greater than the frequency of the input signal.
            harmonic_count:  int, [2~10], The harmonic count of signal.
                             The harmonic frequency is the frequency of the input signal times the counts of harmonics.
                             eg, The input is 20K, the first harmonic is 20K, the second harmonic is 40K,
                                 the third harmonic is 60K, the fourth harmonic is 80K, the fourth harmonic is 100K.
                             The harmonic frequency must be smaller than half the sampling rate.
                             eg, if sampling_rate = 192000, input_signal_frequency = 20000, harmonic_count = 4
                                 so harmonic_frequency = 80000 < 96000.
                             The signal bandwidth  must be greater than harmonic frequency.
            decimation_type: int, [1~255], default 0xFF, sample data decimation.
                             decimation_type is 1 means not to decimate.
                             The smaller the input frequency, the larger the value should be.
            sampling_rate:   int, [0~192000], default 48000, unit Hz, ADC sampling rate.

        Returns:
            dict, {'vpp': value, 'freq': value, 'thd': value, 'thdn': value, 'rms': value, 'noisefloor': value},
            measurement result.
        '''
        assert channel in Audio005Def.AUDIO_CHANNEL_LIST
        assert scope in Audio005Def.LNA_SCOPE

        if self.is_enable_upload is False:
            self.i2s_rx_en_pin.set_level(Audio005Def.I2S_RX_ENABLE)

        pin = self.i2s_ch_select[Audio005Def.SELECT_BIT0]
        pin.set_level(
            Audio005Def.AUDIO_CHANNEL_LIST[channel][Audio005Def.SELECT_BIT0])
        pin = self.i2s_ch_select[Audio005Def.SELECT_BIT1]
        pin.set_level(
            Audio005Def.AUDIO_CHANNEL_LIST[channel][Audio005Def.SELECT_BIT1])

        if scope == Audio005Def.LNA_RANGE_2V:
            self.pca9536.set_pin(Audio005Def.ADC_CH_LIST[channel],
                                 Audio005Def.SEL_GAIN_1)
            gain = Audio005Def.AUDIO_ANALYZER_2V_VREF / Audio005Def.AUDIO_HARDWARE_ATTENUATION
        else:
            self.pca9536.set_pin(Audio005Def.ADC_CH_LIST[channel],
                                 Audio005Def.SEL_GAIN_100)
            gain = Audio005Def.AUDIO_ANALYZER_20mV_VREF / Audio005Def.AUDIO_HARDWARE_ATTENUATION

        index = bisect.bisect(Audio005Def.SAMPLING_RANGE, sampling_rate)

        self.pca9536.set_pin(
            Audio005Def.ADCM0_CTL_BIT,
            Audio005Def.ADCM_CTL_LIST[index][Audio005Def.SELECT_BIT0])
        self.pca9536.set_pin(
            Audio005Def.ADCM1_CTL_BIT,
            Audio005Def.ADCM_CTL_LIST[index][Audio005Def.SELECT_BIT1])

        self.analyzer.disable()
        self.analyzer.enable()
        self.analyzer.analyze_config(sampling_rate, decimation_type,
                                     bandwidth_hz, harmonic_count)
        self.analyzer.analyze()

        freq = self.analyzer.get_frequency()
        vpp = self.analyzer.get_vpp() * gain
        rms = vpp / Audio005Def.RMS_TO_VPP_RATIO

        if scope == Audio005Def.LNA_RANGE_2V:
            index = bisect.bisect(Audio005Def.CAL_RANGE, freq)
            range_name = "AUDIO_2V_" + str(
                Audio005Def.CAL_RANGE[index]) + "Hz_" + channel
        else:
            range_name = "AUDIO_20mV_" + channel

        rms = self.calibrate(range_name, rms)
        vpp = rms * Audio005Def.RMS_TO_VPP_RATIO
        thdn_value = self.analyzer.get_thdn()

        result = dict()
        result["vpp"] = (vpp, Audio005Def.VOLT_UNIT_MV)
        result["freq"] = (freq, Audio005Def.FREQ_UNIT_HZ)
        result["thd"] = (self.analyzer.get_thd(), Audio005Def.THD_UNIT_DB)
        result["thdn"] = (thdn_value, Audio005Def.THDN_UNIT_DB)
        result["rms"] = (rms, Audio005Def.VOLT_UNIT_RMS)
        result["noisefloor"] = (10**(thdn_value / 20) * rms,
                                Audio005Def.VOLT_UNIT_RMS)

        if self.is_enable_upload is False:
            self.i2s_rx_en_pin.set_level(Audio005Def.I2S_RX_DISABLE)

        return result

    def enable_output(self, freq, vpp):
        '''
        Audio005 CS5361 output audio sine waveform.

        Args:
            freq:       int, [5~50000], unit Hz, output signal's frequency.
            vpp:        float, [0~6504], unit mV, output signal's vpp.

        Returns:
            string, "done", execution successful.
        '''
        assert Audio005Def.OUTPUT_FREQ_MIN <= freq
        assert freq <= Audio005Def.OUTPUT_FREQ_MAX
        assert Audio005Def.OUTPUT_VPP_MIN <= vpp
        assert vpp <= Audio005Def.OUTPUT_VPP_MAX

        vpp = self.calibrate(Audio005Def.OUTPUT_CAL_ITEM, vpp)
        vpp = 0 if vpp < 0 else vpp
        # enable I2S tx module
        self.i2s_tx_en_pin.set_level(Audio005Def.AUDIO_OUTPUT_ENABLE)

        self.signal_source.close()
        self.signal_source.open()
        # calculate vpp to vpp scale for FPGA
        vpp_scale = vpp * Audio005Def.VPP_2_SCALE_RATIO
        self.signal_source.set_swg_paramter(Audio005Def.AUDIO_SAMPLING_RATE,
                                            freq, vpp_scale,
                                            Audio005Def.OUTPUT_SIGNAL_DUTY)
        self.signal_source.set_signal_type(Audio005Def.OUTPUT_WAVE)
        self.signal_source.set_signal_time(Audio005Def.SIGNAL_ALWAYS_OUTPUT)
        self.signal_source.output_signal()

        return "done"

    def disable_output(self):
        '''
        Disable Cs5361 output signal.

        Returns:
            string, "done", execution successful.
        '''
        self.signal_source.close()
        self.i2s_tx_en_pin.set_level(Audio005Def.AUDIO_OUTPUT_DISABLE)

        return "done"