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
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"
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 }
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"
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"
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__
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__
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"
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()
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"
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)
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"