Very High measured Ampere peaks
MrGloriousFast opened this issue · comments
Sometimes when i start the measurement it measures insane high peaks of like 9000 ampere.
I am using it in a test stand to test pcb's and the device goes into standby after a few minutes of no activity. To prevent this i wrote the following code to keep it busy:
def watchdog_thread(self):
self.watchdog_is_running = True
while self.watchdog_is_running:
# only do it if we are not using the device for measurements and we were doing nothing for a while
delta_t = (datetime.now()-self.last_update).total_seconds()
if not self.is_running and delta_t > self.watchdog_timeout:
try:
# just some random command so the device doesnt go into standby
self.ppk2_test.get_modifiers()
# alternatively we could make a small measurement, however this creates the same 9000ampere bugs
#self.start()
#time.sleep(1)
#self.get_microampere()
#self.stop()
self.last_update = datetime.now()
except:
pass
#print('self.ppk2_test.get_modifiers() failed in watchdog')
time.sleep(1)
Could this code be the problem creating the insane peak measurements? Note that i am running it in another thread via python multithreading library. Which is not real multi processing as everyone knows.
i am measuring like this:
def start(self):
'''
before doing any measurements you have to set the voltage
and start the measurements
'''
if self.is_running:
return
self.ppk2_test.set_source_voltage(self.volt)
self.ppk2_test.toggle_DUT_power("ON") # Enable DUT power
self.ppk2_test.start_measuring() # Start measuring
and getting ameasurement like so:
def get_microAmpere(self, limit_uA = None):
"""
Get the median microampere value from measurements.
If the value exceeds the critical threshold [limit_uA], perform an emergency shutdown.
note: the shutdown will still take about 5 seconds before the power goes out
"""
# check for default limit value
if limit_uA is None:
limit_uA = self.limit_uA
if self.ppk2_test is not None and self.is_running:
retries = 0
while retries < 5:
read_data = self.ppk2_test.get_data()
if read_data != b'':
samples = self.ppk2_test.get_samples(read_data)
median = statistics.median(samples)
if median >= limit_uA:
self.stop()
print('Critical ampere exceeded. Turning power off')
self.last_update = datetime.now() # update the last communication timestamp
return median
retries += 1
time.sleep(0.01)
return None
i added the limit_uA so that i can test boards that might have a short somehwere on them. This way the power will be cut as soon as a ampere peak is detected. This way i can save components and resolder the board before testing again.
But this limit is getting triggered way too often and if i test the same board with the official software 'power profiler' its obvious that the board is fine and does not pull peaks of 9000 ampere.
Hi @MrGloriousFast,
The PPK2 connection should never timeout, as long as it is connected.
I suspect the start()
and stop()
methods you are using are the cause of the issue. When calling the stop()
method the stop_measuring
command is sent to the ppk2, which takes some time to actually execute and stop the measurements. If the start()
command is issued too early this might cause issues.
Please try using the slightly modified version of the included Power Profiler:
import time
import csv
import datetime
from threading import Thread
# import numpy as np
# import matplotlib.pyplot as plt
# import matplotlib
from ppk2_api.ppk2_api import PPK2_MP as PPK2_API
class PowerProfiler():
def __init__(self, serial_port=None, source_voltage_mV=3300, filename=None):
"""Initialize PPK2 power profiler with serial"""
self.measuring = None
self.measurement_thread = None
self.ppk2 = None
print(f"Initing power profiler")
# try:
if serial_port:
self.ppk2 = PPK2_API(serial_port)
else:
serial_port = self.discover_port()
print(f"Opening serial port: {serial_port}")
if serial_port:
self.ppk2 = PPK2_API(serial_port)
try:
ret = self.ppk2.get_modifiers() # try to read modifiers, if it fails serial port is probably not correct
print(f"Initialized ppk2 api: {ret}")
except Exception as e:
print(f"Error initializing power profiler: {e}")
ret = None
raise e
if not ret:
self.ppk2 = None
raise Exception(f"Error when initing PowerProfiler with serial port {serial_port}")
else:
self.ppk2.use_ampere_meter()
self.source_voltage_mV = source_voltage_mV
self.ppk2.set_source_voltage(self.source_voltage_mV) # set to 3.3V
print(f"Set power profiler source voltage: {self.source_voltage_mV}")
self.measuring = False
self.current_measurements = []
# local variables used to calculate power consumption
self.measurement_start_time = None
self.measurement_stop_time = None
time.sleep(1)
self.stop = False
self.measurement_thread = Thread(target=self.measurement_loop, daemon=True)
self.measurement_thread.start()
# write to csv
self.filename = filename
if self.filename is not None:
with open(self.filename, 'w', newline='') as file:
writer = csv.writer(file)
row = []
for key in ["ts", "avg1000"]:
row.append(key)
writer.writerow(row)
def write_csv_rows(self, samples):
"""Write csv row"""
with open(self.filename, 'a', newline='') as file:
writer = csv.writer(file)
for sample in samples:
row = [datetime.datetime.now().strftime('%d-%m-%Y %H:%M:%S.%f'), sample]
writer.writerow(row)
def delete_power_profiler(self):
"""Join thread"""
self.measuring = False
self.stop = True
print("Deleting power profiler")
if self.measurement_thread:
print(f"Joining measurement thread")
self.measurement_thread.join()
self.measurement_thread = None
if self.ppk2:
print(f"Disabling ppk2 power")
self.disable_power()
del self.ppk2
print(f"Deleted power profiler")
def discover_port(self):
"""Discovers ppk2 serial port"""
ppk2s_connected = PPK2_API.list_devices()
if(len(ppk2s_connected) == 1):
ppk2_port = ppk2s_connected[0]
print(f'Found PPK2 at {ppk2_port}')
return ppk2_port
else:
print(f'Too many connected PPK2\'s: {ppk2s_connected}')
return None
def enable_power(self):
"""Enable ppk2 power"""
if self.ppk2:
self.ppk2.toggle_DUT_power("ON")
return True
return False
def disable_power(self):
"""Disable ppk2 power"""
if self.ppk2:
self.ppk2.toggle_DUT_power("OFF")
return True
return False
def measurement_loop(self):
"""Endless measurement loop will run in a thread"""
while True and not self.stop:
if self.measuring: # read data if currently measuring
read_data = self.ppk2.get_data()
if read_data != b'':
samples, _ = self.ppk2.get_samples(read_data)
self.current_measurements += samples # can easily sum lists, will append individual data
time.sleep(0.001) # TODO figure out correct sleep duration
def _average_samples(self, list, window_size):
"""Average samples based on window size"""
chunks = [list[val:val + window_size] for val in range(0, len(list), window_size)]
avgs = []
for chunk in chunks:
avgs.append(sum(chunk) / len(chunk))
return avgs
def start_measuring(self):
"""Start measuring"""
if not self.measuring: # toggle measuring flag only if currently not measuring
self.current_measurements = [] # reset current measurements
self.measuring = True # set internal flag
self.ppk2.start_measuring() # send command to ppk2
self.measurement_start_time = time.time()
def stop_measuring(self):
"""Stop measuring and return average of period"""
self.measurement_stop_time = time.time()
self.measuring = False
self.ppk2.stop_measuring() # send command to ppk2
#samples_average = self._average_samples(self.current_measurements, 1000)
if self.filename is not None:
self.write_csv_rows(self.current_measurements)
def get_min_current_mA(self):
return min(self.current_measurements) / 1000
def get_max_current_mA(self):
return max(self.current_measurements) / 1000
def get_num_measurements(self):
return len(self.current_measurements)
def get_average_current_mA(self):
"""Returns average current of last measurement in mA"""
if len(self.current_measurements) == 0:
return 0
average_current_mA = (sum(self.current_measurements) / len(self.current_measurements)) / 1000 # measurements are in microamperes, divide by 1000
return average_current_mA
def get_average_power_consumption_mWh(self):
"""Return average power consumption of last measurement in mWh"""
average_current_mA = self.get_average_current_mA()
average_power_mW = (self.source_voltage_mV / 1000) * average_current_mA # divide by 1000 as source voltage is in millivolts - this gives us milliwatts
measurement_duration_h = self.get_measurement_duration_s() / 3600 # duration in seconds, divide by 3600 to get hours
average_consumption_mWh = average_power_mW * measurement_duration_h
return average_consumption_mWh
def get_average_charge_mC(self):
"""Returns average charge in milli coulomb"""
average_current_mA = self.get_average_current_mA()
measurement_duration_s = self.get_measurement_duration_s() # in seconds
return average_current_mA * measurement_duration_s
def get_measurement_duration_s(self):
"""Returns duration of measurement"""
measurement_duration_s = (self.measurement_stop_time - self.measurement_start_time) # measurement duration in seconds
return measurement_duration_s
Use it like so:
p = PowerProfiler()
p.enable_power()
p.start_measuring()
while True:
curr = p.get_average_current_mA()
print(f"Average current: {curr}")
time.sleep(0.001)
This will continously print the average current, you can do the monitoring inside this loop.
@MrGloriousFast does the above suggestion work by any chance?