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 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 start_buffered_acquisition(self, channels, down_sampling_factor=1, down_sampling_calculation='max'): ''' This function enables continuous sampling and data throughput upload to upper stream. Down sampling is supported. For example, when down_sampling_factor =5, down_sampling_calculation=max, select the maximal value from every 5 samples, so the actual data rate is reduced by 5. During continuous sampling, the functions, like configure_input_channel() read(), cannot be called. Args: channels: int, [1, 2], 1 mean the channel for 'voltage', 2 mean the channel for 'current'. down_sampling_factor: int, (>0), default 1, down sample rate for decimation. down_sampling_calculation: string, ['max', 'min'], default 'max'. This parameter takes effect as long as down_sampling_factor is higher than 1. Default 'max'. Exception: InvalidHardwareChannel() if provided channels is not 1 or 2. InvalidRange() if provided down_sampling_factor is not an integer or is <1. InvalidRange() if provided down_sampling_calculation is not 'max' or 'min'. Returns: string, "done", api execution successful. Examples: odin.configure_input_channel(1, 'battery', 1000) odin.start_buffered_acquisition(1) result = odin.read_buffer_statistics(1, 10) print(result) odin.stop_buffered_acquisition(1) ''' if channels not in [1, 2]: raise InvalidHardwareChannel("Invalid channel") if not isinstance(down_sampling_factor, int) or down_sampling_factor < 1: raise InvalidRange("Invalid range") if down_sampling_calculation not in ['max', 'min']: raise InvalidRange("Invalid range") ch_input_config_dict = {1: self.ch1_input_config, 2: self.ch2_input_config} ch_input_config = ch_input_config_dict[channels] pin_id, adc_channel = (OdinDef.READ_CURR_BIT, OdinDef.CHANNEL_1) if ch_input_config['channel'] == 'current' \ else (OdinDef.READ_VOLT_BIT, OdinDef.CHANNEL_0) pin_level = OdinDef.HIGH_LEVEL if ch_input_config['type'] == 'battery' else OdinDef.LOW_LEVEL sampling_rate = ch_input_config['sample_rate'] self.tca9538.set_pin(pin_id, pin_level) self.ad7175.disable_continuous_sampling(adc_channel) time.sleep(0.001) self.ad7175.enable_continuous_sampling(adc_channel, sampling_rate, down_sampling_factor, down_sampling_calculation) self.active_channel = channels self.continuous_sample_mode = True return "done"
def stop_buffered_acquisition(self, session_id): ''' 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 start_buffered_acquisition() function is called. Return 0 for the channels that are not enabled. Args: session_id: int, [1, 2], 1 mean the channel for 'voltage', 2 mean the channel for 'current'. Exception: InvalidHardwareChannel() if provided session_id is not 1 or 2. Returns: string, "done", api execution successful. Examples: odin.configure_input_channel(1, 'battery', 1000) odin.start_buffered_acquisition(1) result = odin.read_buffer_statistics(1, 10) print(result) odin.stop_buffered_acquisition(1) ''' if session_id not in [1, 2]: raise InvalidHardwareChannel("Invalid channel") if not self.active_channel: return 0 channel = self.active_channel ch_input_config_dict = { 1: self.ch1_input_config, 2: self.ch2_input_config } ch_input_config = ch_input_config_dict[channel] if ch_input_config['channel'] == 'current': pin_id, adc_channel = (PSU001014Def.READ_CURR_BIT, PSU001014Def.CHANNEL_1) else: pin_id, adc_channel = (PSU001014Def.READ_VOLT_BIT, PSU001014Def.CHANNEL_0) # set pin to default level. self.tca9538.set_pin(pin_id, PSU001014Def.HIGH_LEVEL) self.ad7175.disable_continuous_sampling(adc_channel) self.active_channel = None self.continuous_sample_mode = False return "done"
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): ''' Get the configuration of the specified output channel. Args: channel: int, [1, 2], 1 mean the channel for 'battery', 2 mean the channel for 'charge'. Exception: InvalidHardwareChannel() if provided channel is not 1 or 2. Returns: dict, actual channel configuration. e.g. {"channel": "charge", "voltage": 4000, "current_limit": 1000}. Examples: config = odin.get_output_channel_configuration(1) print(config) ''' if channel not in [1, 2]: raise InvalidHardwareChannel("Invalid channel") output_config = {1: self.ch1_output_config, 2: self.ch2_output_config} return output_config[channel]
def get_input_channel_configuration(self, channel): ''' Get the configuration of the input channel. Args: channel: int, [1, 2], 1 mean the channel for 'voltage', 2 mean the channel for 'current'. Exception: InvalidHardwareChannel() if provided channel is not 1 or 2. Returns: dict, actual channel configuration. e.g. {"channel": "current", "type": "battery", "sample_rate": 1000, "max_range": 1000}. Examples: config = odin.get_input_channel_configuration(2) print(config) ''' if channel not in [1, 2]: raise InvalidHardwareChannel("Invalid channel") input_config = {1: self.ch1_input_config, 2: self.ch2_input_config} return input_config[channel]
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_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 read_buffer_statistics(self, session_id, samples_per_channel=1, timeout=10.0): ''' This function takes a number of samples to calculate RMS/average/max/min value of the set of sampled value. Values read are in mV or mA, which is determined by DRI. This function can only be called in continuous mode, a.k.a, after configure_input_channel(), start_buffered_acquisition() function is called. Return 0 for the channels that are not enabled. The returned value is calibrated if calibration mode is `cal`. The data read back from the voltage channel is calibrated in mV. The data read back in the current channel is calibrated in uA when max_range is 1, and it is calibrated in mA when max_range is 1000. During continuous sampling, the functions, like configure_input_channel() read(), cannot be called. Args: session_id: int, [1, 2], 1 mean the channel for 'voltage', 2 mean the channel for 'current'. samples_per_channel: int, [1~512], defualt 1, samples count taken for calculation. timeout: float, (>=0), default 10.0, the maximum duration the acquisition can take, in seconds, before returning an exception. Exception: InvalidHardwareChannel() if provided session_id is not 1 or 2. InvalidSampleCount() if provided samples_per_channel is not an integer or is <1 or >512. InvalidTimeout() if provided timeout is <0. Returns: dict, the channel data to be measured. { (rms, unit), (avg, unit), (max, unit), (min, unit) }, for voltage channel #1 current channel #2. Examples: odin.configure_input_channel(1, 'battery', 1000) odin.start_buffered_acquisition(1) result = odin.read_buffer_statistics(1, 10) print(result) odin.stop_buffered_acquisition(1) ''' if session_id not in [1, 2]: raise InvalidHardwareChannel("Invalid channel") if not isinstance(samples_per_channel, int) or samples_per_channel < 1 or samples_per_channel > 512: raise InvalidSampleCount("Invalid sample count") if timeout < 0: raise InvalidTimeout("Invalid timeout") if not self.continuous_sample_mode: return 0 if not self.active_channel: return 0 channel = self.active_channel channel_data = list() result = dict() ch_input_config_dict = {1: self.ch1_input_config, 2: self.ch2_input_config} ch_input_config = ch_input_config_dict[channel] pin_id, adc_channel = (OdinDef.READ_CURR_BIT, OdinDef.CHANNEL_1) if ch_input_config['channel'] == 'current' \ else (OdinDef.READ_VOLT_BIT, OdinDef.CHANNEL_0) pin_level = OdinDef.HIGH_LEVEL if ch_input_config['type'] == 'battery' else OdinDef.LOW_LEVEL self.tca9538.set_pin(pin_id, pin_level) if channel == 1: cal_type = OdinDef.BATT_VOLT_READ if ch_input_config['type'] == 'battery' else OdinDef.CHARGE_VOLT_READ else: if ch_input_config['type'] == 'battery': if ch_input_config['max_range'] == 1: cal_type = OdinDef.BATT_CURR_READ_1mA elif ch_input_config['max_range'] == 1000: cal_type = OdinDef.BATT_CURR_READ_1A else: cal_type = OdinDef.CHARGE_CURR_READ try: channel_data = self.ad7175.get_continuous_sampling_voltage(adc_channel, samples_per_channel) except Exception as e: self.stop_buffered_acquisition(session_id) if (OdinDef.ADC_ERROR_BIT & self.ad7175.read_register(OdinDef.STATUS_REG_ADDR)): reg_data = self.ad7175.read_register(OdinDef.DATA_REG_ADDR) if channel == 1: if ch_input_config['type'] == 'battery': volt_range = OdinDef.BATT_VOLT_RANGE else: volt_range = OdinDef.CHARGE_VOLT_RANGE if reg_data == 0xFFFFFF: raise OdinException('Overrange! the voltage value exceeds the {}V range'.format(volt_range)) elif reg_data == 0x000000: raise OdinException('Underrange! the voltage value is lower than \ negative {}V range'.format(volt_range)) else: raise OdinException("{}".format(e.message)) else: if ch_input_config['type'] == 'battery': if ch_input_config['max_range'] == 1: curr_range = OdinDef.BATT_CURR_RANGE_1mA else: curr_range = OdinDef.BATT_CURR_RANGE_1A else: curr_range = OdinDef.CHARGE_CURR_RANGE if reg_data == 0xFFFFFF: raise OdinException('Overrange! the current value exceeds the {}A range'.format(curr_range)) elif reg_data == 0x000000: raise OdinException('Underrange! the current value is lower than \ negative {}A range'.format(curr_range)) else: raise OdinException("{}".format(e.message)) else: raise OdinException("{}".format(e.message)) min_data = min(channel_data) max_data = max(channel_data) avg_data = sum(channel_data) / len(channel_data) rms_data = math.sqrt(sum([x**2 for x in channel_data]) / len(channel_data)) if channel == 1: unit = OdinDef.UNIT_mV # battery channel: Vout = Vadc * 2.5, charge channel: Vout = Vadc * 4.0 min_data, max_data, avg_data, rms_data = map(lambda volt: volt * 2.5 if ch_input_config['type'] == 'battery' else volt * 4.0, [min_data, max_data, avg_data, rms_data]) avg_data = self.calibrate(cal_type, avg_data) # set pin to default level. if ch_input_config['type'] == 'charge': self.tca9538.set_pin(OdinDef.READ_VOLT_BIT, OdinDef.HIGH_LEVEL) else: unit = OdinDef.UNIT_mA if ch_input_config['type'] == 'battery': min_data, max_data, avg_data, rms_data = map(lambda volt: volt, [min_data, max_data, avg_data, rms_data]) else: # charge channel: I = (Vadc - 24.7mV) / 2 min_data, max_data, avg_data, rms_data = map(lambda volt: (volt - 24.7) / 2.0, [min_data, max_data, avg_data, rms_data]) avg_data = self.calibrate(cal_type, avg_data) if ch_input_config['max_range'] == 1: # uA convert to mA min_data, max_data, avg_data, rms_data = map(lambda volt: volt / 1000.0, [min_data, max_data, avg_data, rms_data]) # set pin to default level. if ch_input_config['type'] == 'charge': self.tca9538.set_pin(OdinDef.READ_CURR_BIT, OdinDef.HIGH_LEVEL) result['rms'] = (rms_data, unit + 'rms') result['avg'] = (avg_data, unit) result['max'] = (max_data, unit) result['min'] = (min_data, unit) return result
def read_buffer(self, session_id, samples_per_channel=1, timeout=10.0): ''' This function takes a number of samples raw data of the set of sampled value. Values read are in mV, which is determined by DRI. This function can only be called in continuous mode, a.k.a, after configure_input_channel(), start_buffered_acquisition() function is called. Return 0 for the channels that are not enabled. During continuous sampling, the functions, like configure_input_channel() read(), cannot be called. Args: session_id: int, [1, 2], 1 mean the channel for 'voltage', 2 mean the channel for 'current'. samples_per_channel: int, [1~512], defualt 1, samples count taken for calculation. timeout: float, (>=0), default 10.0, the maximum duration the acquisition can take, in seconds, before returning an exception. Exception: InvalidHardwareChannel() if provided session_id is not 1 or 2. InvalidSampleCount() if provided samples_per_channel is not an integer or is <1 or >512. InvalidTimeout() if provided timeout is <0. Returns: list, [value,...] the unit of elements in the list is mV. Examples: odin.configure_input_channel(1, 'battery', 1000) odin.start_buffered_acquisition(1) result = odin.read_buffer(1, 10) print(result) odin.stop_buffered_acquisition(1) ''' if session_id not in [1, 2]: raise InvalidHardwareChannel("Invalid channel") if not isinstance(samples_per_channel, int) or samples_per_channel < 1 or samples_per_channel > 512: raise InvalidSampleCount("Invalid sample count") if timeout < 0: raise InvalidTimeout("Invalid timeout") if not self.continuous_sample_mode: return 0 if not self.active_channel: return 0 raw_data = list() channel = self.active_channel ch_input_config_dict = {1: self.ch1_input_config, 2: self.ch2_input_config} ch_input_config = ch_input_config_dict[channel] pin_id, adc_channel = (OdinDef.READ_CURR_BIT, OdinDef.CHANNEL_1) if ch_input_config['channel'] == 'current' \ else (OdinDef.READ_VOLT_BIT, OdinDef.CHANNEL_0) pin_level = OdinDef.HIGH_LEVEL if ch_input_config['type'] == 'battery' else OdinDef.LOW_LEVEL self.tca9538.set_pin(pin_id, pin_level) try: raw_data = self.ad7175.get_continuous_sampling_voltage(adc_channel, samples_per_channel) except Exception as e: self.stop_buffered_acquisition(session_id) if (OdinDef.ADC_ERROR_BIT & self.ad7175.read_register(OdinDef.STATUS_REG_ADDR)): reg_data = self.ad7175.read_register(OdinDef.DATA_REG_ADDR) if channel == 1: if ch_input_config['type'] == 'battery': volt_range = OdinDef.BATT_VOLT_RANGE else: volt_range = OdinDef.CHARGE_VOLT_RANGE if reg_data == 0xFFFFFF: raise OdinException('Overrange! the voltage value exceeds the {}V range'.format(volt_range)) elif reg_data == 0x000000: raise OdinException('Underrange! the voltage value is lower than \ negative {}V range'.format(volt_range)) else: raise OdinException("{}".format(e.message)) else: if ch_input_config['type'] == 'battery': if ch_input_config['max_range'] == 1: curr_range = OdinDef.BATT_CURR_RANGE_1mA else: curr_range = OdinDef.BATT_CURR_RANGE_1A else: curr_range = OdinDef.CHARGE_CURR_RANGE if reg_data == 0xFFFFFF: raise OdinException('Overrange! the current value exceeds the {}A range'.format(curr_range)) elif reg_data == 0x000000: raise OdinException('Underrange! the current value is lower than \ negative {}A range'.format(curr_range)) else: raise OdinException("{}".format(e.message)) else: raise OdinException("{}".format(e.message)) return raw_data
def read(self, channels, samples_per_channel=1, timeout=10.0): ''' Reads the specified number of samples from each channel. Values read are in mV or mA, which is determined by DRI. The returned value is calibrated if calibration mode is `cal`. The data read back from the voltage channel is calibrated in mV. The data read back in the current channel is calibrated in uA when max_range is 1, and it is calibrated in mA when max_range is 1000. Args: channels: int, [1, 2], 1 mean the channel for 'voltage', 2 mean the channel for 'current'. samples_per_channel: int, (>=1), default 1, the number of samples to read from each channel. timeout: float, (>=0), default 10.0, the maximum duration the acquisition can take, in seconds, before returning an exception. Exception: InvalidHardwareChannel() if provided channel is not 1 or 2. InvalidSampleCount() if provided samples_per_channel is not an integer or is <1. InvalidTimeout() if provided timeout is <0. Returns: list, [value1, ..., valueN], measured value defined by configure_input_channel() voltage channel always in mV current channel always in mA Examples: odin.configure_input_channel(2, 'battery', 1000, 1000) result = odin.read(2) print(result) ''' if channels not in [1, 2]: raise InvalidHardwareChannel("Invalid channel") if not isinstance(samples_per_channel, int) or samples_per_channel < 1: raise InvalidSampleCount("Invalid sample count") if timeout < 0: raise InvalidTimeout("Invalid timeout") target_data = list() if channels == 1: if self.ch1_input_config['type'] == 'battery': cal_type = OdinDef.BATT_VOLT_READ self.tca9538.set_pin(OdinDef.READ_VOLT_BIT, OdinDef.HIGH_LEVEL) elif self.ch1_input_config['type'] == 'charge': cal_type = OdinDef.CHARGE_VOLT_READ self.tca9538.set_pin(OdinDef.READ_VOLT_BIT, OdinDef.LOW_LEVEL) for x in range(samples_per_channel): try: volt_raw = self.ad7175.read_volt(OdinDef.CHANNEL_0) except Exception as e: if (OdinDef.ADC_ERROR_BIT & self.ad7175.read_register(OdinDef.STATUS_REG_ADDR)): reg_data = self.ad7175.read_register(OdinDef.DATA_REG_ADDR) if self.ch1_input_config['type'] == 'battery': volt_range = OdinDef.BATT_VOLT_RANGE else: volt_range = OdinDef.CHARGE_VOLT_RANGE if reg_data == 0xFFFFFF: raise OdinException('Overrange! the voltage value exceeds the {}V range'.format(volt_range)) elif reg_data == 0x000000: raise OdinException('Underrange! the voltage value is lower than \ negative {}V range'.format(volt_range)) else: raise OdinException("{}".format(e.message)) else: raise OdinException("{}".format(e.message)) # if channel is battery: Vout = Vadc * 2.5, if channel is charge: Vout = Vadc * 4 volt = map(lambda x: (x * 2.5) if self.ch1_input_config['type'] == 'battery' else (x * 4.0), [volt_raw])[0] volt = self.calibrate(cal_type, volt) target_data.append(volt) # set pin to default level. if self.ch1_input_config['type'] == 'charge': self.tca9538.set_pin(OdinDef.READ_VOLT_BIT, OdinDef.HIGH_LEVEL) else: if self.ch2_input_config['type'] == 'battery': if self.ch2_input_config['max_range'] == 1: cal_type = OdinDef.BATT_CURR_READ_1mA elif self.ch2_input_config['max_range'] == 1000: cal_type = OdinDef.BATT_CURR_READ_1A self.tca9538.set_pin(OdinDef.READ_CURR_BIT, OdinDef.HIGH_LEVEL) elif self.ch2_input_config['type'] == 'charge': cal_type = OdinDef.CHARGE_CURR_READ self.tca9538.set_pin(OdinDef.READ_CURR_BIT, OdinDef.LOW_LEVEL) for x in range(samples_per_channel): try: volt = self.ad7175.read_volt(OdinDef.CHANNEL_1) except Exception as e: if (OdinDef.ADC_ERROR_BIT & self.ad7175.read_register(OdinDef.STATUS_REG_ADDR)): reg_data = self.ad7175.read_register(OdinDef.DATA_REG_ADDR) if self.ch2_input_config['type'] == 'battery': if self.ch2_input_config['max_range'] == 1: curr_range = OdinDef.BATT_CURR_RANGE_1mA else: curr_range = OdinDef.BATT_CURR_RANGE_1A else: curr_range = OdinDef.CHARGE_CURR_RANGE if reg_data == 0xFFFFFF: raise OdinException('Overrange! the current value exceeds the {}A range'.format(curr_range)) elif reg_data == 0x000000: raise OdinException('Underrange! the current value is lower than \ negative {}A range'.format(curr_range)) else: raise OdinException("{}".format(e.message)) else: raise OdinException("{}".format(e.message)) # if channel is battery: I = Vadc, if channel is charge: I = (Vadc - 24.7mV) / 2 current = (volt - 24.7) / 2.0 if self.ch2_input_config['type'] == 'charge' else volt current = self.calibrate(cal_type, current) if self.ch2_input_config['max_range'] == 1: # uA convert to mA current = current / 1000.0 target_data.append(current) # set pin to default level. if self.ch2_input_config['type'] == 'charge': self.tca9538.set_pin(OdinDef.READ_CURR_BIT, OdinDef.HIGH_LEVEL) return target_data
def configure_input_channel(self, channel, type, sample_rate, max_range=None): ''' Configure input channel. Args: channel: int, [1, 2], 1 mean the channel for 'voltage', 2 mean the channel for 'current'. type: string, ['battery', 'charge'], types are supported. sample_rate: float, [5~250000], unit is Hz, adc measure sampling rate, which not continuouse, please refer ad7175 datasheet. max_range: int, [1, 1000], unit is mA, maximum range of current, only supported for 'current' of 'battery' Exception: InvalidHardwareChannel() if provided channel is not 1 or 2. InvalidHardwareChannelType() if provided type is not "battery" or "charge". InvalidSampleRate() if sample_rate is bad. InvalidRange() if max_range are bad values. Returns: dict, actual channel configuration. e.g. {"channel": "current", "type": "battery", "sample_rate": 1000, "max_range": 1000}. Examples: config = odin.configure_input_channel(2, 'battery', 1000, 1000) print(config) ''' if channel not in [1, 2]: raise InvalidHardwareChannel("Invalid channel") if type not in ['battery', 'charge']: raise InvalidHardwareChannelType("Invalid channel type") if sample_rate < 5 or sample_rate > 250000: raise InvalidSampleRate("Invalid sampling rate range") if max_range or max_range == 0: if max_range not in [1, 1000]: raise InvalidRange("Invalid range") pin_level = {'battery': OdinDef.HIGH_LEVEL, 'charge': OdinDef.LOW_LEVEL} input_config = {1: self.ch1_input_config, 2: self.ch2_input_config} if channel == 1: self.tca9538.set_pin(OdinDef.READ_VOLT_BIT, pin_level[type]) self.ad7175.set_sampling_rate(OdinDef.CHANNEL_0, sample_rate) self.ch1_input_config['channel'] = 'voltage' self.ch1_input_config['type'] = type self.ch1_input_config['sample_rate'] = sample_rate self.ch1_input_config['max_range'] = None else: self.tca9538.set_pin(OdinDef.READ_CURR_BIT, pin_level[type]) self.ad7175.set_sampling_rate(OdinDef.CHANNEL_1, sample_rate) self.ch2_input_config['channel'] = 'current' self.ch2_input_config['type'] = type self.ch2_input_config['sample_rate'] = sample_rate if type == 'battery': if max_range == 1: self.ch2_input_config['max_range'] = 1 self.tca9538.set_pin(OdinDef.BATT_READ_CURR_RANGE_BIT, OdinDef.HIGH_LEVEL) else: self.ch2_input_config['max_range'] = 1000 self.tca9538.set_pin(OdinDef.BATT_READ_CURR_RANGE_BIT, OdinDef.LOW_LEVEL) else: self.ch2_input_config['max_range'] = None return input_config[channel]
def configure_output_channel(self, channel, voltage, current_limit=None): ''' Configure output channel. The set voltage and current_limit is calibrated if calibration mode is `cal`. The voltage is calibrated in mV and the current_limit is calibrated in mA. Args: channel: int, [1, 2], 1 mean the channel for 'battery', 2 mean the channel for 'charge'. voltage: float/int, [0~7500], unit is mV, the output voltage value. 0 effectively disables output. 'battery' output range is 0~4800 mV, 'charge' output range is 0~7500 mV. current_limit: float/int, [0~1000], unit is mA, default None, only supported for charge output. Exception: InvalidHardwareChannel() if provided channel is not 1 or 2. InvalidRange() if the provided arguments voltage or current_limit are out of range. Returns: dict, actual channel configuration. e.g. {"channel": "charge", "voltage": 4000, "current_limit": 1000}. Examples: config = odin.configure_output_channel(1, 4000, 1000) print(config) ''' if channel not in [1, 2]: raise InvalidHardwareChannel("Invalid channel") if voltage < OdinDef.DAC_RANGE_DICT[channel]["min"] or voltage > OdinDef.DAC_RANGE_DICT[channel]["max"]: raise InvalidRange("Invalid voltage range") if current_limit or current_limit == 0: if current_limit < 0 or current_limit > 1000: raise InvalidRange("Invalid current_limit range") dac_output = {1: self.ad5667_batt, 2: self.ad5667_charge} output_config = {1: self.ch1_output_config, 2: self.ch2_output_config} ad5667 = dac_output[channel] if channel == 1: self.ch1_output_config['channel'] = 'battery' self.ch1_output_config['voltage'] = voltage self.ch1_output_config['current_limit'] = None self.tca9538.set_pin(OdinDef.BATT_ENABLE_BIT, OdinDef.HIGH_LEVEL) vdac = self.calibrate(OdinDef.BATT, voltage) enable_bit = OdinDef.BATT_ENABLE_BIT else: self.ch2_output_config['channel'] = 'charge' self.ch2_output_config['voltage'] = voltage self.tca9538.set_pin(OdinDef.CHARGE_ENABLE_BIT, OdinDef.HIGH_LEVEL) volt = self.calibrate(OdinDef.CHARGE, voltage) vdac = volt / 2.0 enable_bit = OdinDef.CHARGE_ENABLE_BIT if vdac > OdinDef.MAX_VOLTAGE: vdac = OdinDef.MAX_VOLTAGE if vdac < OdinDef.VOLTAGE_MIN: vdac = OdinDef.VOLTAGE_MIN if voltage == 0: ad5667.output_volt_dc(OdinDef.CHANNEL_0, OdinDef.VOLTAGE_MIN) if channel == 1: self.tca9538.set_pin(enable_bit, OdinDef.LOW_LEVEL) else: ad5667.output_volt_dc(OdinDef.CHANNEL_0, vdac) if current_limit and channel == 2: self.ch2_output_config['current_limit'] = current_limit threshold = self.calibrate(OdinDef.CHARGE_CURR_SET, current_limit) # I = (Vdac - 24.7) / 2 vdac = threshold * 2.0 + 24.7 if vdac > OdinDef.MAX_VOLTAGE: vdac = OdinDef.MAX_VOLTAGE if vdac < OdinDef.VOLTAGE_MIN: vdac = OdinDef.VOLTAGE_MIN self.ad5667_charge.output_volt_dc(OdinDef.CHANNEL_1, vdac) return output_config[channel]
def read_buffer(self, session_id, samples_per_channel=1, timeout=10.0): ''' This function takes a number of samples raw data of the set of sampled value. Values read are in mV, which is determined by DRI. This function can only be called in continuous mode, a.k.a, after configure_input_channel(), start_buffered_acquisition() function is called. Return 0 for the channels that are not enabled. During continuous sampling, the functions, like configure_input_channel() read(), cannot be called. Args: session_id: int, [1, 2], 1 mean the channel for 'voltage', 2 mean the channel for 'current'. samples_per_channel: int, [1~512], defualt 1, samples count taken for calculation. timeout: float, (>=0), default 10.0, the maximum duration the acquisition can take, in seconds, before returning an exception. Exception: InvalidHardwareChannel() if provided session_id is not 1 or 2. InvalidSampleCount() if provided samples_per_channel is not an integer or is <1 or >512. InvalidTimeout() if provided timeout is <0. Returns: list, [value,...] the unit of elements in the list is mV. Examples: odin.configure_input_channel(1, 'battery', 1000) odin.start_buffered_acquisition(1) result = odin.read_buffer(1, 10) print(result) odin.stop_buffered_acquisition(1) ''' if session_id not in [1, 2]: raise InvalidHardwareChannel("Invalid channel") if not isinstance( samples_per_channel, int) or samples_per_channel < 1 or samples_per_channel > 512: raise InvalidSampleCount("Invalid sample count") if timeout < 0: raise InvalidTimeout("Invalid timeout") if not self.continuous_sample_mode: return 0 if not self.active_channel: return 0 raw_data = list() channel = self.active_channel ch_input_config_dict = { 1: self.ch1_input_config, 2: self.ch2_input_config } ch_input_config = ch_input_config_dict[channel] if ch_input_config['channel'] == 'current': pin_id, adc_channel = (PSU001014Def.READ_CURR_BIT, PSU001014Def.CHANNEL_1) else: pin_id, adc_channel = (PSU001014Def.READ_VOLT_BIT, PSU001014Def.CHANNEL_0) pin_level = PSU001014Def.HIGH_LEVEL if ch_input_config[ 'type'] == 'battery' else PSU001014Def.LOW_LEVEL self.tca9538.set_pin(pin_id, pin_level) try: raw_data = self.ad7175.get_continuous_sampling_voltage( adc_channel, samples_per_channel) except Exception: raise return raw_data
def configure_output_channel(self, channel, voltage, current_limit=None): ''' Configure output channel. The set voltage and current_limit is calibrated if calibration mode is `cal`. The voltage is calibrated in mV and the current_limit is calibrated in mA. Args: channel: int, [1, 2], 1 mean the channel for 'battery', 2 mean the channel for 'charge'. voltage: float/int, [0~7500], unit is mV, the output voltage value. 0 effectively disables output. 'battery' output range is 0~4800 mV, 'charge' output range is 0~7500 mV. current_limit: float/int, [0~1100], unit is mA, default None, only supported for charge output, recommended range is [0~1000], [0~1100] is just for debug. Exception: InvalidHardwareChannel() if provided channel is not 1 or 2. InvalidRange() if the provided arguments voltage or current_limit are out of range. Returns: dict, actual channel configuration. e.g. {"channel": "charge", "voltage": 4000, "current_limit": 1000}. Examples: config = odin.configure_output_channel(1, 4000, 1000) print(config) ''' if channel not in [1, 2]: raise InvalidHardwareChannel("Invalid channel") if voltage < PSU001014Def.DAC_RANGE_DICT[channel]["min"]: raise InvalidRange("Invalid voltage range") elif voltage > PSU001014Def.DAC_RANGE_DICT[channel]["max"]: raise InvalidRange("Invalid voltage range") if current_limit or current_limit == 0: if current_limit < 0 or current_limit > 1100: raise InvalidRange("Invalid current_limit range") dac_output = {1: self.ad5667_batt, 2: self.ad5667_charge} output_config = {1: self.ch1_output_config, 2: self.ch2_output_config} ad5667 = dac_output[channel] if channel == 1: self.ch1_output_config['channel'] = 'battery' self.ch1_output_config['voltage'] = voltage self.ch1_output_config['current_limit'] = None self.tca9538.set_pin(PSU001014Def.BATT_ENABLE_BIT, PSU001014Def.HIGH_LEVEL) vdac = self.calibrate(PSU001014Def.BATT, voltage) enable_bit = PSU001014Def.BATT_ENABLE_BIT else: if voltage > PSU001014Def.CHARGE_FLAG_VOLT: self.tca9538.set_pin(PSU001014Def.CHARGE_FLAG_BIT, PSU001014Def.HIGH_LEVEL) else: self.tca9538.set_pin(PSU001014Def.CHARGE_FLAG_BIT, PSU001014Def.LOW_LEVEL) self.ch2_output_config['channel'] = 'charge' self.ch2_output_config['voltage'] = voltage if voltage == PSU001014Def.DAC_RANGE_DICT[channel]["min"]: self.tca9538.set_pin(PSU001014Def.CHARGE_ENABLE_BIT, PSU001014Def.LOW_LEVEL) else: self.tca9538.set_pin(PSU001014Def.CHARGE_ENABLE_BIT, PSU001014Def.LOW_LEVEL) volt = self.calibrate(PSU001014Def.CHARGE, voltage) vdac = volt / 2.0 enable_bit = PSU001014Def.CHARGE_ENABLE_BIT if vdac > PSU001014Def.MAX_VOLTAGE: vdac = PSU001014Def.MAX_VOLTAGE if vdac < PSU001014Def.VOLTAGE_MIN: vdac = PSU001014Def.VOLTAGE_MIN if channel == 2: # V_dcdc = R1 * (0.6 / R2 + 0.6 / R3 - VDC_SET / R3) + 0.6, R1=165000, R2=10000, R3=100000 # VDC_SET = R3 * (0.6 / R2 + 0.6 / R3 -(V_dcdc - 0.6) / R1) # = 0.6 * (R3 / R2 + 1) - R3 * (V_dcdc -0.6) / R1 v_dcdc = (voltage + self.volt_diff) / 1000.0 r1, r2, r3 = 165000, 10000, 100000 vdc_set = (0.6 * (r3 / r2 + 1) - r3 * (v_dcdc - 0.6) / r1) * 1000.0 if voltage < PSU001014Def.CHARGE_VOLT_DEF: vdc_set -= PSU001014Def.CHARGE_DIFF if vdc_set < PSU001014Def.MCP4725_MIN_VOLTAGE: vdc_set = PSU001014Def.MCP4725_MIN_VOLTAGE elif vdc_set > PSU001014Def.MCP4725_MAX_VOLTAGE: vdc_set = PSU001014Def.MCP4725_MAX_VOLTAGE if self.charge_current_vdac <= vdac: self.mcp4725.fast_output_volt_dc(vdc_set) elif channel == 1: # V_dcdc= R1 * (0.6 / R2 + 0.6 / R3 - VDC_SET / R3) + 0.6, R1=75000, R2=10000, R3=100000 # VDC_SET = R3 * (0.6 / R2 + 0.6 / R3 -(V_dcdc - 0.6) / R1) # = 0.6 * (R3 / R2 + 1) - R3 * (V_dcdc -0.6) / R1 if voltage <= PSU001014Def.BATT_VOLT_REF: v_dcdc = 2.5 else: v_dcdc = (voltage + PSU001014Def.BATT_DIFF) / 1000.0 r1, r2, r3 = 75000, 10000, 100000 vdc_set = (0.6 * (r3 / r2 + 1) - r3 * (v_dcdc - 0.6) / r1) * 1000.0 if vdc_set > PSU001014Def.MAX_VOLTAGE: vdc_set = PSU001014Def.MAX_VOLTAGE elif vdc_set < PSU001014Def.VOLTAGE_MIN: vdc_set = PSU001014Def.VOLTAGE_MIN if self.battery_current_vdac <= vdac: self.ad5667_batt.output_volt_dc(PSU001014Def.CHANNEL_1, vdc_set) if voltage == 0 and channel == 1: ad5667.output_volt_dc(PSU001014Def.CHANNEL_0, PSU001014Def.VOLTAGE_MIN) self.tca9538.set_pin(enable_bit, PSU001014Def.LOW_LEVEL) else: ad5667.output_volt_dc(PSU001014Def.CHANNEL_0, vdac) if channel == 2: if self.charge_current_vdac > vdac: self.mcp4725.fast_output_volt_dc(vdc_set) self.charge_current_vdac = vdac if current_limit and channel == 2: self.ch2_output_config['current_limit'] = current_limit threshold = self.calibrate(PSU001014Def.CHARGE_CURR_SET, current_limit) # I = (Vdac - 24.7) / 2 vdac = threshold * 2.0 + 24.7 if vdac > PSU001014Def.MAX_VOLTAGE: vdac = PSU001014Def.MAX_VOLTAGE if vdac < PSU001014Def.VOLTAGE_MIN: vdac = PSU001014Def.VOLTAGE_MIN self.ad5667_charge.output_volt_dc(PSU001014Def.CHANNEL_1, vdac) return output_config[channel]