summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Friesel <derf@finalrewind.org>2018-11-05 15:58:06 +0100
committerDaniel Friesel <derf@finalrewind.org>2018-11-05 15:58:06 +0100
commit62eccea6ba50cac4ce280137f5e272f7ec09f9d2 (patch)
tree2377ac17cbd90670b2851a36b40f3935d2ff4453
parent471b17b7abbd82854c9239a15148b0cae854a686 (diff)
add threshold autodetection, improve human/machine readable output
-rwxr-xr-xbin/msp430-etv108
1 files changed, 92 insertions, 16 deletions
diff --git a/bin/msp430-etv b/bin/msp430-etv
index 810c7ce..98b6893 100755
--- a/bin/msp430-etv
+++ b/bin/msp430-etv
@@ -2,6 +2,7 @@
# vim:tabstop=4:softtabstop=4:shiftwidth=4:textwidth=160:smarttab:expandtab
import getopt
+import itertools
import matplotlib.pyplot as plt
import numpy as np
import os
@@ -45,21 +46,48 @@ OPTIONS
--skip <count>
Skip <count> data samples. This is useful to avoid startup code
influencing the results of a long-running measurement
- --threshold <watts>|auto
+ --threshold <watts>|mean
Partition data into points with mean power >= <watts> and points with
mean power < <watts>, and print some statistics. higher power is handled
as peaks, whereas low-power measurements constitute the baseline.
- If the threshold is set to "auto", the mean power of all measurements
+ If the threshold is set to "mean", the mean power of all measurements
will be used
+ --threshold-peakcount <num>
+ Automatically determine threshold so that there are exactly <num> peaks.
+ A peaks is a group of consecutive measurements with mean power >= threshold
--plot
Show power/time plot
--stat
Show mean voltage, current, and power as well as total energy consumption.
''')
+def peak_search(data, lower, upper, direction_function):
+ while upper - lower > 1e-6:
+ bs_test = np.mean([lower, upper])
+ peakcount = itertools.groupby(data, lambda x: x >= bs_test)
+ peakcount = filter(lambda x: x[0] == True, peakcount)
+ peakcount = sum(1 for i in peakcount)
+ direction = direction_function(peakcount, bs_test)
+ if direction == 0:
+ return bs_test
+ elif direction == 1:
+ lower = bs_test
+ else:
+ upper = bs_test
+ return None
+
+def peak_search2(data, lower, upper, check_function):
+ for power in np.arange(lower, upper, 1e-6):
+ peakcount = itertools.groupby(data, lambda x: x >= power)
+ peakcount = filter(lambda x: x[0] == True, peakcount)
+ peakcount = sum(1 for i in peakcount)
+ if check_function(peakcount, power) == 0:
+ return power
+ return None
+
if __name__ == '__main__':
try:
- optspec = ('help load= save= skip= threshold= plot stat')
+ optspec = ('help load= save= skip= threshold= threshold-peakcount= plot stat')
raw_opts, args = getopt.getopt(sys.argv[1:], "", optspec.split(' '))
for option, parameter in raw_opts:
@@ -78,9 +106,12 @@ if __name__ == '__main__':
else:
opt['skip'] = 0
- if 'threshold' in opt and opt['threshold'] != 'auto':
+ if 'threshold' in opt and opt['threshold'] != 'mean':
opt['threshold'] = float(opt['threshold'])
+ if 'threshold-peakcount' in opt:
+ opt['threshold-peakcount'] = int(opt['threshold-peakcount'])
+
except getopt.GetoptError as err:
print(err)
sys.exit(2)
@@ -120,20 +151,53 @@ if __name__ == '__main__':
print('Calculated energy: U*I*t = {:f} J'.format(m_calc_energy))
print('Energy deviation: {:.1f}%'.format(m_energy_deviation * 100))
+ power = data[:, 1] * data[:, 2]
+
+ if 'threshold-peakcount' in opt:
+ bs_mean = np.mean(power)
+
+ # Finding the correct threshold is tricky. If #peaks < peakcont, our
+ # current threshold may be too low (extreme case: a single peaks
+ # containing all measurements), but it may also be too high (extreme
+ # case: a single peak containing just one data point). Similarly,
+ # #peaks > peakcount may be due to baseline noise causing lots of
+ # small peaks, or due to peak noise (if the threshold is already rather
+ # high).
+ # For now, we first try a simple binary search:
+ # The threshold is probably somewhere around the mean, so if
+ # #peaks != peakcount and threshold < mean, we go up, and if
+ # #peaks != peakcount and threshold >= mean, we go down.
+ # If that doesn't work, we fall back to a linear search in 1 µW steps
+ def direction_function(peakcount, power):
+ if peakcount == opt['threshold-peakcount']:
+ return 0;
+ if power < bs_mean:
+ return 1;
+ return -1;
+ threshold = peak_search(power, np.min(power), np.max(power), direction_function)
+ if threshold == None:
+ threshold = peak_search2(power, np.min(power), np.max(power), direction_function)
+
+ if threshold != None:
+ print('Threshold set to {:.0f} µW : {:.9f}'.format(threshold * 1e6, threshold))
+ opt['threshold'] = threshold
+ else:
+ print('Found no working threshold')
if 'threshold' in opt:
- power = data[:, 1] * data[:, 2]
-
- if opt['threshold'] == 'auto':
+ if opt['threshold'] == 'mean':
opt['threshold'] = np.mean(power)
- print('Threshold set to {:.0f} µW'.format(opt['threshold'] * 1e6))
+ print('Threshold set to {:.0f} µW : {:.9f}'.format(opt['threshold'] * 1e6, opt['threshold']))
+ baseline_mean = 0
if np.any(power < opt['threshold']):
- print('Baseline mean: {:.0f} µW'.format(
- np.mean(power[power < opt['threshold']]) * 1e6 ))
+ baseline_mean = np.mean(power[power < opt['threshold']])
+ print('Baseline mean: {:.0f} µW : {:.9f}'.format(
+ baseline_mean * 1e6, baseline_mean))
if np.any(power >= opt['threshold']):
- print('Peak mean: {:.0f} µW'.format(
- np.mean(power[power >= opt['threshold']]) * 1e6))
+ print('Peak mean: {:.0f} µW : {:.9f}'.format(
+ np.mean(power[power >= opt['threshold']]) * 1e6,
+ np.mean(power[power >= opt['threshold']])))
peaks = []
peak_start = -1
@@ -144,23 +208,35 @@ if __name__ == '__main__':
peaks.append((peak_start, i))
peak_start = -1
+ total_energy = 0
+ delta_energy = 0
for peak in peaks:
duration = data[peak[1]-1, 0] - data[peak[0], 0]
+ total_energy += np.mean(power[peak[0] : peak[1]]) * duration
+ delta_energy += (np.mean(power[peak[0] : peak[1]]) - baseline_mean) * duration
print('{:.2f}ms peak ({:f} -> {:f})'.format(duration * 1000,
data[peak[0], 0], data[peak[1]-1, 0]))
print(' {:f} µJ / mean {:f} µW'.format(
np.mean(power[peak[0] : peak[1]]) * duration * 1e6,
np.mean(power[peak[0] : peak[1]]) * 1e6 ))
+ print('Peak energy mean: {:.0f} µJ : {:.9f}'.format(
+ total_energy * 1e6 / len(peaks), total_energy / len(peaks)))
+ print('Average per-peak energy (delta over baseline): {:.0f} µJ : {:.9f}'.format(
+ delta_energy * 1e6 / len(peaks), delta_energy / len(peaks)))
+
if 'save' in opt:
with open(opt['save'], 'w') as f:
f.write(log_data)
if 'stat' in opt:
- print('Mean voltage: {:f}'.format(np.mean(data[:, 2])))
- print('Mean current: {:f}'.format(np.mean(data[:, 1])))
- print('Mean power: {:f}'.format(np.mean(data[:, 1] * data[:, 2])))
- print('Total energy: {:f}'.format(m_energy))
+ mean_voltage = np.mean(data[:, 2])
+ mean_current = np.mean(data[:, 1])
+ mean_power = np.mean(data[:, 1] * data[:, 2])
+ print('Mean voltage: {:.2f} V : {:.9f}'.format(mean_voltage, mean_voltage))
+ print('Mean current: {:.0f} µA : {:.9f}'.format(mean_current * 1e6, mean_current))
+ print('Mean power: {:.0f} µW : {:.9f}'.format(mean_power * 1e6, mean_power))
+ print('Total energy: {:f} J : {:.9f}'.format(m_energy, m_energy))
if 'plot' in opt:
pwrhandle, = plt.plot(data[:, 0], data[:, 1] * data[:, 2], 'b-', label='U*I', markersize=1)