summaryrefslogtreecommitdiff
path: root/extra
diff options
context:
space:
mode:
authorNick Sanders <nsanders@chromium.org>2016-11-22 16:20:47 -0800
committerchrome-bot <chrome-bot@chromium.org>2016-12-16 20:56:49 -0800
commitccff3365038c037741d274c6713f72f129766980 (patch)
tree13766cecd3fb98c4b49c0e4c3bbe585333eb5b77 /extra
parent1016bdfd11ff4fae8e5a72e6561ca38b3d91b4cb (diff)
downloadchrome-ec-ccff3365038c037741d274c6713f72f129766980.tar.gz
sweetberry: add power logging tool
powerlog.py can access sweetberry and log power data. Also included are marlin and kevin example board configs. BUG=chromium:608039 TEST=log power data BRANCH=None Change-Id: I0f868d95d17d86522dca045a227a824563f93cd0 Signed-off-by: Nick Sanders <nsanders@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/413293 Reviewed-by: Aseda Aboagye <aaboagye@chromium.org>
Diffstat (limited to 'extra')
-rw-r--r--extra/usb_power/board.README71
-rw-r--r--extra/usb_power/board/kevin/kevin.board18
-rw-r--r--extra/usb_power/board/kevin/kevin_all.scenario18
-rw-r--r--extra/usb_power/board/marlin/marlin.board74
-rw-r--r--extra/usb_power/board/marlin/marlin_all_A.scenario42
-rw-r--r--extra/usb_power/board/marlin/marlin_all_B.scenario28
-rw-r--r--extra/usb_power/board/marlin/marlin_common.scenario1
-rw-r--r--extra/usb_power/board/marlin/marlin_short.scenario1
-rw-r--r--extra/usb_power/board/marlin/marlin_vbat.scenario1
-rwxr-xr-xextra/usb_power/powerlog.py549
10 files changed, 803 insertions, 0 deletions
diff --git a/extra/usb_power/board.README b/extra/usb_power/board.README
new file mode 100644
index 0000000000..e253660496
--- /dev/null
+++ b/extra/usb_power/board.README
@@ -0,0 +1,71 @@
+Sweetberry USB power monitoring
+
+This tool allows high speed monitoring of power rails via a special USB
+endpoint. Currently this is implemented for the sweetberry baord.
+
+To use on a board, you'll need two config files, one describing the board,
+a ".board" file, and one describing the particular rails you want to
+monitor in this session, a ".scenario" file.
+
+
+Board files:
+
+Board files contain a list of rails, supporting 48 channels each on up to two
+sweetberries. For each rail you must specify a name, sense resistor value,
+and channel number. You can optionally list expected voltage and net name.
+The format is as follows, in json:
+
+example.board:
+[
+{ "name": "railname",
+ "rs": <sense resistor value in ohms>,
+ "sweetberry": <"A" for main sweetberry, "B" for a secondary sweetberry>,
+ "channel": <0-47 according to board schematic>,
+ "v": <optional expected bus voltage in volts>,
+ "net": <optional schematic net name>
+},
+{...}
+]
+
+Scenario files:
+
+Scenario files contain the set of rails to monitor in this session. The
+file format is simply a list of rail names from the board file.
+
+example.scenario:
+[
+"railname",
+"another_railname",
+...
+]
+
+
+Output:
+powerlog.py will output a csv formatted log to stdout, at timing intervals
+specified on the command line. Currently values below "-t 10000" do not work
+reliably but further updates should allow faster updating.
+
+An example run of:
+./powerlog.py -b board/marlin/marlin.board -c board/marlin/marlin_short.scenario -t 100000
+
+Will result in:
+ts:32976us, VBAT uW, VDD_MEM uW, VDD_CORE uW, VDD_GFX uW, VDD_1V8_PANEL uW
+0.033004, 12207.03, 4882.81, 9155.27, 2441.41, 0.00
+0.066008, 12207.03, 3662.11, 9155.27, 2441.41, 0.00
+0.099012, 12207.03, 3662.11, 9155.27, 2441.41, 0.00
+...
+
+
+The output format is as follows:
+ts:32976us: Timestamps either zero based or synced to system clock,
+ in seconds. The column header indicates the selected
+ sampling interval. Since the INA231 has specific
+ hardware defines sampling options, this will be the
+ closest supported option lower than the requested "-t"
+ value on the command line.
+VBAT uW: microwatt reading from this rail, generated on the INA
+ by integrating the voltage/amperage on the sense resistor
+ over the sampling time, and multiplying by the sampled bus
+ voltage.
+... uW: Further microwatt entry columns for each rail specified in
+ your scenario file.
diff --git a/extra/usb_power/board/kevin/kevin.board b/extra/usb_power/board/kevin/kevin.board
new file mode 100644
index 0000000000..8ab59573c6
--- /dev/null
+++ b/extra/usb_power/board/kevin/kevin.board
@@ -0,0 +1,18 @@
+[
+{"name": "pp5000", "rs": 0.01, "sweetberry": "A", "channel": 0},
+{"name": "ppvar_gpu", "rs": 0.01, "sweetberry": "A", "channel": 1},
+{"name": "pp3300_wifi_bt", "rs": 0.01, "sweetberry": "A", "channel": 2},
+{"name": "pp1500_ap_io", "rs": 0.01, "sweetberry": "A", "channel": 3},
+{"name": "pp3300_alw", "rs": 0.01, "sweetberry": "A", "channel": 4},
+{"name": "ppvar_litcpu", "rs": 0.01, "sweetberry": "A", "channel": 5},
+{"name": "pp1800_s0", "rs": 0.01, "sweetberry": "A", "channel": 6},
+{"name": "pp3300_haven", "rs": 0.1, "sweetberry": "A", "channel": 7},
+{"name": "ppvar_bigcpu", "rs": 0.01, "sweetberry": "A", "channel": 8},
+{"name": "pp900_ap", "rs": 0.01, "sweetberry": "A", "channel": 9},
+{"name": "pp1800_ec", "rs": 0.1, "sweetberry": "A", "channel": 10},
+{"name": "pp1800_sensor", "rs": 0.01, "sweetberry": "A", "channel": 11},
+{"name": "pp1800_alw", "rs": 0.01, "sweetberry": "A", "channel": 12},
+{"name": "pp1200_lpddr", "rs": 0.01, "sweetberry": "A", "channel": 13},
+{"name": "pp3300_ec", "rs": 0.1, "sweetberry": "A", "channel": 14},
+{"name": "pp3300_s0", "rs": 0.01, "sweetberry": "A", "channel": 15}
+]
diff --git a/extra/usb_power/board/kevin/kevin_all.scenario b/extra/usb_power/board/kevin/kevin_all.scenario
new file mode 100644
index 0000000000..dbc3953364
--- /dev/null
+++ b/extra/usb_power/board/kevin/kevin_all.scenario
@@ -0,0 +1,18 @@
+[
+"pp5000",
+"ppvar_gpu",
+"pp3300_wifi_bt",
+"pp1500_ap_io",
+"pp3300_alw",
+"ppvar_litcpu",
+"pp1800_s0",
+"pp3300_haven",
+"ppvar_bigcpu",
+"pp900_ap",
+"pp1800_ec",
+"pp1800_sensor",
+"pp1800_alw",
+"pp1200_lpddr",
+"pp3300_ec",
+"pp3300_s0"
+]
diff --git a/extra/usb_power/board/marlin/marlin.board b/extra/usb_power/board/marlin/marlin.board
new file mode 100644
index 0000000000..dc4cdad258
--- /dev/null
+++ b/extra/usb_power/board/marlin/marlin.board
@@ -0,0 +1,74 @@
+[
+{"name": "VBAT", "rs": 0.01, "sweetberry": "A", "net": "", "channel": 0},
+{"name": "VBAT_", "rs": 0.01, "sweetberry": "B", "net": "", "channel": 0},
+{"name": "VDD_MEM", "rs": 0.05, "sweetberry": "A", "net": "V_MEM_0V875", "channel": 1},
+{"name": "VDD_EBI_PHY", "rs": 0.1, "sweetberry": "A", "net": "V_EBI_0V875", "channel": 2},
+{"name": "VDD_PCIE_1P8", "rs": 0.5, "sweetberry": "A", "net": "V_USB_1V8", "channel": 3},
+{"name": "VDD_PCIE_CORE", "rs": 0.1, "sweetberry": "A", "net": "V_PCIE_0V925", "channel": 4},
+{"name": "VDD_MIPI_CSI", "rs": 0.1, "sweetberry": "A", "net": "V_CSI_DSI_1V25", "channel": 5},
+{"name": "VDD_A1", "rs": 0.1, "sweetberry": "A", "net": "V_MSMA1_1V225", "channel": 6},
+{"name": "VDD_A2", "rs": 1.0, "sweetberry": "A", "net": "V_MSMA2_1V8", "channel": 7},
+{"name": "VDD_P2", "rs": 0.02, "sweetberry": "A", "net": "V_IO_1V8", "channel": 8},
+{"name": "VDD_P3", "rs": 0.5, "sweetberry": "B", "net": "V_IO_1V8", "channel": 1},
+{"name": "VDD_P5", "rs": 0.5, "sweetberry": "A", "net": "V_RUIM1", "channel": 9},
+{"name": "VDD_P6", "rs": 0.02, "sweetberry": "A", "net": "V_IO_1V8", "channel": 46},
+{"name": "VDD_P10", "rs": 0.1, "sweetberry": "A", "net": "V_UFS_1V2", "channel": 10},
+{"name": "VDD_P12", "rs": 0.1, "sweetberry": "A", "net": "V_SRIO_1V8", "channel": 11},
+{"name": "VDD_USB_HS_3P1", "rs": 0.5, "sweetberry": "A", "net": "V_USB_3V075", "channel": 12},
+{"name": "VDD_CORE", "rs": 0.02, "sweetberry": "A", "net": "V_VDDCORE_0V8", "channel": 13},
+{"name": "VDD_GFX", "rs": 0.05, "sweetberry": "A", "net": "V_GFX_0V98", "channel": 14},
+{"name": "VDD_MODEM", "rs": 0.05, "sweetberry": "A", "net": "V_MODEM_1V0", "channel": 15},
+{"name": "VDD_APC", "rs": 0.01, "sweetberry": "A", "net": "V_APC_0V8", "channel": 16},
+{"name": "VDD_P1", "rs": 0.1, "sweetberry": "A", "net": "V_DDRCORE_1V1", "channel": 17},
+{"name": "VDD_DDR_CORE_1P8", "rs": 0.1, "sweetberry": "B", "net": "V_IO_1V8", "channel": 3},
+{"name": "VDD_SSC_CORE", "rs": 0.05, "sweetberry": "A", "net": "V_SSCCORE_0V8", "channel": 18},
+{"name": "VDD_SSC_MEM", "rs": 0.02, "sweetberry": "A", "net": "V_SSCMEM_0V875", "channel": 19},
+{"name": "V_EMMC_2V95", "rs": 0.1, "sweetberry": "A", "net": "V_EMMC_2V95", "channel": 20},
+{"name": "VCCQ2", "rs": 0.1, "sweetberry": "B", "net": "V_IO_1V8", "channel": 4},
+{"name": "VDD/VDDIO", "rs": 1.0, "sweetberry": "B", "net": "V_SRIO_1V8", "channel": 5},
+{"name": "V_LED_3V3", "rs": 0.1, "sweetberry": "A", "net": "V_LED_3V3", "channel": 21},
+{"name": "VDD/VIO", "rs": 1.0, "sweetberry": "B", "net": "V_SRIO_1V8", "channel": 6},
+{"name": "V_SRIO_1V8", "rs": 1.0, "sweetberry": "B", "net": "V_SRIO_1V8", "channel": 7},
+{"name": "V_SRIO_1V8_", "rs": 1.0, "sweetberry": "B", "net": "V_SRIO_1V8", "channel": 8},
+{"name": "VBAT/VDD/VDDA", "rs": 0.1, "sweetberry": "B", "net": "V_SRIO_1V8", "channel": 9},
+{"name": "V_SRIO_1V8__", "rs": 1.0, "sweetberry": "B", "net": "V_SRIO_1V8", "channel": 10},
+{"name": "V_SR_2V85", "rs": 0.1, "sweetberry": "A", "net": "V_SR_2V85", "channel": 22},
+{"name": "", "rs": 0.1, "sweetberry": "B", "net": "", "channel": 11},
+{"name": "V_USBSS_SW_1V8", "rs": 0.1, "sweetberry": "A", "net": "V_USBSS_SW_1V8", "channel": 23},
+{"name": "V_RF_2V7", "rs": 0.5, "sweetberry": "A", "net": "V_RF_2V7", "channel": 24},
+{"name": "V_TP_3V3", "rs": 0.5, "sweetberry": "A", "net": "V_TP_3V3", "channel": 25},
+{"name": "V_ELVDD", "rs": 0.1, "sweetberry": "B", "net": "V_ELVDD", "channel": 16},
+{"name": "V_AVDD", "rs": 1.0, "sweetberry": "B", "net": "V_AVDD", "channel": 17},
+{"name": "VCI_3V", "rs": 0.1, "sweetberry": "A", "net": "VCI_3V", "channel": 26},
+{"name": "VDD_1V8_PANEL", "rs": 0.5, "sweetberry": "A", "net": "VDD_1V8_PANEL", "channel": 27},
+{"name": "V_TP_1V8", "rs": 0.1, "sweetberry": "A", "net": "V_TP_1V8", "channel": 28},
+{"name": "V_CAM2_D1V2", "rs": 0.1, "sweetberry": "A", "net": "V_CAM2_D1V2", "channel": 31},
+{"name": "V_CAMIO_1V8", "rs": 0.1, "sweetberry": "A", "net": "V_CAMIO_1V8", "channel": 32},
+{"name": "V_CAM1_VCM2V85", "rs": 0.5, "sweetberry": "B", "net": "V_CAM1_VCM2V85", "channel": 18},
+{"name": "V_CAM1_A2V85", "rs": 1.0, "sweetberry": "B", "net": "V_CAM1_A2V85", "channel": 19},
+{"name": "V_CAM1_D1V0", "rs": 0.02, "sweetberry": "A", "net": "V_CAM1_D1V0", "channel": 33},
+{"name": "V_CAMIO_1V8_", "rs": 1.0, "sweetberry": "B", "net": "V_CAMIO_1V8", "channel": 20},
+{"name": "VBAT_ADC_IN", "rs": 0.01, "sweetberry": "A", "net": "V_DCIN", "channel": 34},
+{"name": "VDD_RX", "rs": 1.0, "sweetberry": "B", "net": "V_IO_1V8", "channel": 21},
+{"name": "VDD_MIC_BIAS", "rs": 0.01, "sweetberry": "A", "net": "V_BOOST_BYPASS", "channel": 35},
+{"name": "PVDD/VDD", "rs": 0.1, "sweetberry": "A", "net": "V_AUD_AMP_3V3", "channel": 36},
+{"name": "V_DCIN", "rs": 0.01, "sweetberry": "A", "net": "V_DCIN", "channel": 47},
+{"name": "V_AUDIO_2V15", "rs": 0.02, "sweetberry": "A", "net": "V_AUDIO_2V15", "channel": 38},
+{"name": "V_AUDIO_1V3", "rs": 0.02, "sweetberry": "A", "net": "V_AUDIO_1V3", "channel": 39},
+{"name": "PVIN/AVIN", "rs": 0.02, "sweetberry": "A", "net": "V_DCIN", "channel": 40},
+{"name": "VBATT", "rs": 0.5, "sweetberry": "B", "net": "VPA_BATT", "channel": 24},
+{"name": "VCC_GSM", "rs": 0.01, "sweetberry": "B", "net": "VPA_APT", "channel": 25},
+{"name": "VCC1_3G", "rs": 0.01, "sweetberry": "B", "net": "VPA", "channel": 26},
+{"name": "VAPT", "rs": 0.01, "sweetberry": "B", "net": "VPA_APT", "channel": 27},
+{"name": "VCC1", "rs": 0.01, "sweetberry": "B", "net": "VPA", "channel": 28},
+{"name": "VPA_BATT", "rs": 0.5, "sweetberry": "B", "net": "VPA_BATT", "channel": 29},
+{"name": "VDD_RF1_TVCO", "rs": 0.1, "sweetberry": "A", "net": "VREG_RF_1P0", "channel": 42},
+{"name": "V_GPS_1V8", "rs": 1.0, "sweetberry": "A", "net": "V_GPS_1V8", "channel": 43},
+{"name": "VDDIO_XTAL", "rs": 1.0, "sweetberry": "A", "net": "VDDIO_XTAL_1V8", "channel": 44},
+{"name": "VDD_FEM", "rs": 0.05, "sweetberry": "A", "net": "V_DCIN", "channel": 45},
+{"name": "VDD33", "rs": 0.1, "sweetberry": "B", "net": "V_VDDRF_3V2", "channel": 31},
+{"name": "DVDD11", "rs": 0.1, "sweetberry": "B", "net": "V_VDDRF_1V1", "channel": 32},
+{"name": "VDD(PAD)", "rs": 0.5, "sweetberry": "B", "net": "V_NFC_1V8", "channel": 33},
+{"name": "VBAT/VBAT2/VDD(UP)", "rs": 0.1, "sweetberry": "B", "net": "V_MBAT", "channel": 34},
+{"name": "NFC_5V_BOOST", "rs": 0.1, "sweetberry": "B", "net": "NFC_5V_BOOST", "channel": 35}
+]
diff --git a/extra/usb_power/board/marlin/marlin_all_A.scenario b/extra/usb_power/board/marlin/marlin_all_A.scenario
new file mode 100644
index 0000000000..a024b698d7
--- /dev/null
+++ b/extra/usb_power/board/marlin/marlin_all_A.scenario
@@ -0,0 +1,42 @@
+[ "VBAT",
+"VDD_MEM",
+"VDD_EBI_PHY",
+"VDD_PCIE_1P8",
+"VDD_PCIE_CORE",
+"VDD_MIPI_CSI",
+"VDD_A1",
+"VDD_A2",
+"VDD_P2",
+"VDD_P5",
+"VDD_P6",
+"VDD_P10",
+"VDD_P12",
+"VDD_USB_HS_3P1",
+"VDD_CORE",
+"VDD_GFX",
+"VDD_MODEM",
+"VDD_APC",
+"VDD_P1",
+"VDD_SSC_CORE",
+"VDD_SSC_MEM",
+"V_EMMC_2V95",
+"V_LED_3V3",
+"V_SR_2V85",
+"V_USBSS_SW_1V8",
+"V_RF_2V7",
+"V_TP_3V3",
+"VCI_3V",
+"VDD_1V8_PANEL",
+"V_TP_1V8",
+"V_CAM2_D1V2",
+"V_CAMIO_1V8",
+"V_CAM1_D1V0",
+"VBAT_ADC_IN",
+"VDD_MIC_BIAS",
+"PVDD/VDD",
+"V_DCIN",
+"V_AUDIO_2V15",
+"V_AUDIO_1V3",
+"VDD_RF1_TVCO",
+"V_GPS_1V8",
+"VDD_FEM"]
diff --git a/extra/usb_power/board/marlin/marlin_all_B.scenario b/extra/usb_power/board/marlin/marlin_all_B.scenario
new file mode 100644
index 0000000000..876d2dbfbd
--- /dev/null
+++ b/extra/usb_power/board/marlin/marlin_all_B.scenario
@@ -0,0 +1,28 @@
+[ "VBAT_",
+"VDD_P3",
+"VDD_DDR_CORE_1P8",
+"VCCQ2",
+"VDD/VDDIO",
+"VDD/VIO",
+"V_SRIO_1V8",
+"V_SRIO_1V8_",
+"VBAT/VDD/VDDA",
+"V_SRIO_1V8__",
+"",
+"V_ELVDD",
+"V_AVDD",
+"V_CAM1_VCM2V85",
+"V_CAM1_A2V85",
+"V_CAMIO_1V8_",
+"VDD_RX",
+"VBATT",
+"VCC_GSM",
+"VCC1_3G",
+"VAPT",
+"VCC1",
+"VPA_BATT",
+"VDD33",
+"DVDD11",
+"VDD(PAD)",
+"VBAT/VBAT2/VDD(UP)",
+"NFC_5V_BOOST" ]
diff --git a/extra/usb_power/board/marlin/marlin_common.scenario b/extra/usb_power/board/marlin/marlin_common.scenario
new file mode 100644
index 0000000000..7e20236c34
--- /dev/null
+++ b/extra/usb_power/board/marlin/marlin_common.scenario
@@ -0,0 +1 @@
+["VBAT", "VDD_MEM", "VDD_EBI_PHY", "VDD_PCIE_1P8", "VDD_PCIE_CORE", "VDD_MIPI_CSI", "VDD_A1", "VDD_CORE", "VDD_GFX", "VDD_MODEM", "VDD_APC", "VDD_P1", "VDD_SSC_CORE", "VDD_SSC_MEM", "VDD_1V8_PANEL", "V_CAM2_D1V2", "VBAT_ADC_IN", "VDD_FEM"]
diff --git a/extra/usb_power/board/marlin/marlin_short.scenario b/extra/usb_power/board/marlin/marlin_short.scenario
new file mode 100644
index 0000000000..2cfc8b0f9a
--- /dev/null
+++ b/extra/usb_power/board/marlin/marlin_short.scenario
@@ -0,0 +1 @@
+["VBAT", "VDD_MEM", "VDD_CORE", "VDD_GFX", "VDD_1V8_PANEL"]
diff --git a/extra/usb_power/board/marlin/marlin_vbat.scenario b/extra/usb_power/board/marlin/marlin_vbat.scenario
new file mode 100644
index 0000000000..f1c18ca202
--- /dev/null
+++ b/extra/usb_power/board/marlin/marlin_vbat.scenario
@@ -0,0 +1 @@
+["VBAT"]
diff --git a/extra/usb_power/powerlog.py b/extra/usb_power/powerlog.py
new file mode 100755
index 0000000000..ebe95b8e26
--- /dev/null
+++ b/extra/usb_power/powerlog.py
@@ -0,0 +1,549 @@
+#!/usr/bin/python
+# Copyright 2016 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Program to fetch power logging data from a sweetberry device
+ or other usb device that exports a USB power logging interface.
+"""
+import argparse
+import array
+import json
+import struct
+import sys
+import time
+from pprint import pprint
+
+import usb
+
+# This can be overridden by -v.
+debug = False
+def debuglog(msg):
+ if debug:
+ print msg
+
+def logoutput(msg):
+ print msg
+ sys.stdout.flush()
+
+
+class Spower(object):
+ """Power class to access devices on the bus.
+
+ Usage:
+ bus = Spower()
+
+ Instance Variables:
+ _logger: Sgpio tagged log output
+ _dev: pyUSB device object
+ _read_ep: pyUSB read endpoint for this interface
+ _write_ep: pyUSB write endpoint for this interface
+ """
+
+ INA231 = 1
+
+ # Map between header channel number (0-47)
+ # and INA I2C bus/addr on sweetberry.
+ CHMAP = {
+ 0: (3, 0x40),
+ 1: (1, 0x40),
+ 2: (2, 0x40),
+ 3: (0, 0x40),
+ 4: (3, 0x41),
+ 5: (1, 0x41),
+ 6: (2, 0x41),
+ 7: (0, 0x41),
+ 8: (3, 0x42),
+ 9: (1, 0x42),
+ 10: (2, 0x42),
+ 11: (0, 0x42),
+ 12: (3, 0x43),
+ 13: (1, 0x43),
+ 14: (2, 0x43),
+ 15: (0, 0x43),
+ 16: (3, 0x44),
+ 17: (1, 0x44),
+ 18: (2, 0x44),
+ 19: (0, 0x44),
+ 20: (3, 0x45),
+ 21: (1, 0x45),
+ 22: (2, 0x45),
+ 23: (0, 0x45),
+ 24: (3, 0x46),
+ 25: (1, 0x46),
+ 26: (2, 0x46),
+ 27: (0, 0x46),
+ 28: (3, 0x47),
+ 29: (1, 0x47),
+ 30: (2, 0x47),
+ 31: (0, 0x47),
+ 32: (3, 0x48),
+ 33: (1, 0x48),
+ 34: (2, 0x48),
+ 35: (0, 0x48),
+ 36: (3, 0x49),
+ 37: (1, 0x49),
+ 38: (2, 0x49),
+ 39: (0, 0x49),
+ 40: (3, 0x4a),
+ 41: (1, 0x4a),
+ 42: (2, 0x4a),
+ 43: (0, 0x4a),
+ 44: (3, 0x4b),
+ 45: (1, 0x4b),
+ 46: (2, 0x4b),
+ 47: (0, 0x4b),
+ }
+
+ def __init__(self, vendor=0x18d1,
+ product=0x5020, interface=1, serialname=None):
+ # Find the stm32.
+ dev_list = usb.core.find(idVendor=vendor, idProduct=product, find_all=True)
+ if dev_list is None:
+ raise Exception("Power", "USB device not found")
+
+ # Check if we have multiple stm32s and we've specified the serial.
+ dev = None
+ if serialname:
+ for d in dev_list:
+ dev_serial = "PyUSB dioesn't have a stable interface"
+ try:
+ dev_serial = usb.util.get_string(d, 256, d.iSerialNumber)
+ except:
+ dev_serial = usb.util.get_string(d, d.iSerialNumber)
+ if dev_serial == serialname:
+ dev = d
+ break
+ if dev is None:
+ raise SusbError("USB device(%s) not found" % serialname)
+ else:
+ try:
+ dev = dev_list[0]
+ except:
+ dev = dev_list.next()
+
+ debuglog("Found USB device: %04x:%04x" % (vendor, product))
+ self._dev = dev
+
+ # Get an endpoint instance.
+ try:
+ dev.set_configuration()
+ except:
+ pass
+ cfg = dev.get_active_configuration()
+
+ intf = usb.util.find_descriptor(cfg, custom_match=lambda i: \
+ i.bInterfaceClass==255 and i.bInterfaceSubClass==0x54)
+
+ self._intf = intf
+ debuglog("InterfaceNumber: %s" % intf.bInterfaceNumber)
+
+ read_ep = usb.util.find_descriptor(
+ intf,
+ # match the first IN endpoint
+ custom_match = \
+ lambda e: \
+ usb.util.endpoint_direction(e.bEndpointAddress) == \
+ usb.util.ENDPOINT_IN
+ )
+
+ self._read_ep = read_ep
+ debuglog("Reader endpoint: 0x%x" % read_ep.bEndpointAddress)
+
+ write_ep = usb.util.find_descriptor(
+ intf,
+ # match the first OUT endpoint
+ custom_match = \
+ lambda e: \
+ usb.util.endpoint_direction(e.bEndpointAddress) == \
+ usb.util.ENDPOINT_OUT
+ )
+
+ self._write_ep = write_ep
+ debuglog("Writer endpoint: 0x%x" % write_ep.bEndpointAddress)
+
+ self.clear_ina_struct()
+
+ debuglog("Found power logging USB endpoint.")
+
+ def clear_ina_struct(self):
+ """ Clear INA description struct."""
+ self._inas = []
+
+ def append_ina_struct(self, name, rs, port, addr, data=None):
+ """Add an INA descriptor into the list of active INAs.
+
+ Args:
+ name: Readable name of this channel.
+ rs: Sense resistor value in ohms, floating point.
+ port: I2C channel this INA is connected to.
+ addr: I2C addr of this INA.
+ data: Misc data for special handling, board specific.
+ """
+ ina = {}
+ ina['name'] = name
+ ina['rs'] = rs
+ ina['port'] = port
+ ina['addr'] = addr
+ # Calculate INA231 Calibration register
+ # (see INA231 spec p.15)
+ # CurrentLSB = uA per div = 80mV / (Rsh * 2^15)
+ # CurrentLSB uA = 80000000nV / (Rsh mOhm * 0x8000)
+ ina['uAscale'] = 80000000. / (rs * 0x8000);
+ ina['uWscale'] = 25. * ina['uAscale'];
+ ina['data'] = data
+ self._inas.append(ina)
+
+ def wr_command(self, write_list, read_count=1, wtimeout=100, rtimeout=1000):
+ """Write command to logger logic.
+
+ This function writes byte command values list to stm, then reads
+ byte status.
+
+ Args:
+ write_list: list of command byte values [0~255].
+ read_count: number of status byte values to read.
+
+ Interface:
+ write: [command, data ... ]
+ read: [status ]
+
+ Returns:
+ bytes read, or None on failure.
+ """
+ debuglog("Spower.wr_command(write_list=[%s] (%d), read_count=%s)" % (
+ list(bytearray(write_list)), len(write_list), read_count))
+
+ # Clean up args from python style to correct types.
+ write_length = 0
+ if write_list:
+ write_length = len(write_list)
+ if not read_count:
+ read_count = 0
+
+ # Send command to stm32.
+ if write_list:
+ cmd = write_list
+ ret = self._write_ep.write(cmd, wtimeout)
+
+ debuglog("RET: %s " % ret)
+
+ # Read back response if necessary.
+ if read_count:
+ bytesread = self._read_ep.read(512, rtimeout)
+ debuglog("BYTES: [%s]" % bytesread)
+
+ if len(bytesread) != read_count:
+ pass
+
+ debuglog("STATUS: 0x%02x" % int(bytesread[0]))
+ if read_count == 1:
+ return bytesread[0]
+ else:
+ return bytesread
+
+ return None
+
+ def clear(self):
+ """Clear pending reads on the stm32"""
+ try:
+ while True:
+ ret = self.wr_command("", read_count=512, rtimeout=100, wtimeout=50)
+ debuglog("Try Clear: read %s" % "success" if ret == 0 else "failure")
+ except:
+ pass
+
+ def send_reset(self):
+ """Reset the power interface on the stm32"""
+ cmd = struct.pack("<H", 0x0000)
+ ret = self.wr_command(cmd, rtimeout=50, wtimeout=50)
+ debuglog("Command RESET: %s" % "success" if ret == 0 else "failure")
+
+ def reset(self):
+ """Try resetting the USB interface until success
+
+ Throws:
+ Exception on failure.
+ """
+ count = 10
+ while count > 0:
+ self.clear()
+ try:
+ self.send_reset()
+ count = 0
+ except Exception as e:
+ self.clear()
+ self.clear()
+ debuglog("TRY %d of 10: %s" % (count, e))
+ finally:
+ count -= 1
+ if count == 0:
+ raise Exception("Power", "Failed to reset")
+
+ def stop(self):
+ """Stop any active data acquisition."""
+ cmd = struct.pack("<H", 0x0001)
+ ret = self.wr_command(cmd)
+ debuglog("Command STOP: %s" % "success" if ret == 0 else "failure")
+
+ def start(self, integration_us):
+ """Start data acquisition.
+
+ Args:
+ integration_us: int, how many us between samples, and
+ how often the data block must be read.
+
+ Returns:
+ actual sampling interval in ms.
+ """
+ cmd = struct.pack("<HI", 0x0003, integration_us)
+ read = self.wr_command(cmd, read_count=5)
+ actual_us = 0
+ if len(read) == 5:
+ ret, actual_us = struct.unpack("<BI", read)
+ debuglog("Command START: %s %dus" % ("success" if ret == 0 else "failure", actual_us))
+ else:
+ debuglog("Command START: FAIL")
+
+ title = "ts:%dus" % actual_us
+ for i in range(0, len(self._inas)):
+ name = self._inas[i]['name']
+ title += ", %s uW" % name
+ logoutput(title)
+
+ return actual_us
+
+ def add_ina_name(self, name):
+ """Add INA from board config.
+
+ Args:
+ name: readable name of power rail in board config.
+
+ Raises:
+ Exception on failure.
+ """
+ for datum in self._brdcfg:
+ if datum["name"] == name:
+ channel = int(datum["channel"])
+ rs = int(float(datum["rs"]) * 1000.)
+
+ port, addr = self.CHMAP[channel]
+ self.add_ina(port, self.INA231, addr, 0, rs, data=datum)
+ return
+ raise Exception("Power", "Failed to find INA %s" % name)
+
+ def set_time(self, timestamp_us):
+ """Set sweetberry tie to match host time.
+
+ Args:
+ timestamp_us: host timestmap in us.
+ """
+ # 0x0005 , 8 byte timestamp
+ cmd = struct.pack("<HQ", 0x0005, timestamp_us)
+ ret = self.wr_command(cmd)
+
+ debuglog("Command SETTIME: %s" % "success" if ret == 0 else "failure")
+
+ def add_ina(self, bus, ina_type, addr, extra, resistance, data=None):
+ """Add an INA to the data acquisition list.
+
+ Args:
+ bus: which i2c bus the INA is on. Same ordering as Si2c.
+ ina_type: which model INA. 0x1 -> INA231
+ addr: 7 bit i2c addr of this INA
+ extra: extra data for nonstandard configs.
+ resistance: int, shunt resistance in mOhm
+ """
+ # 0x0002, 1B: bus, 1B:INA type, 1B: INA addr, 1B: extra, 4B: Rs
+ cmd = struct.pack("<HBBBBI", 0x0002, bus, ina_type, addr, extra, resistance)
+ ret = self.wr_command(cmd)
+ if ret == 0:
+ if data:
+ name = data['name']
+ else:
+ name = "ina%d_%02x" % (bus, addr)
+ self.append_ina_struct(name, resistance, bus, addr, data=data)
+ debuglog("Command ADD_INA: %s" % "success" if ret == 0 else "failure")
+
+ def report_header_size(self):
+ """Helper function to calculate power record header size."""
+ result = 2
+ timestamp = 8
+ return result + timestamp
+
+ def report_size(self, ina_count):
+ """Helper function to calculate full power record size."""
+ record = 2
+
+ datasize = self.report_header_size() + ina_count * record
+ # Round to multiple of 4 bytes.
+ datasize = int(((datasize + 3) / 4) * 4)
+
+ return datasize
+
+ def read_line(self):
+ """Read a line of data fromteh setup INAs
+
+ Output:
+ stdout of the record retrieved in csv format.
+ """
+ try:
+ expected_bytes = self.report_size(len(self._inas))
+ cmd = struct.pack("<H", 0x0004)
+ bytesread = self.wr_command(cmd, read_count=expected_bytes)
+ except usb.core.USBError as e:
+ print "READ LINE FAILED %s" % e
+ return
+
+ if len(bytesread) == 1:
+ if bytesread[0] != 0x6:
+ debuglog("READ LINE FAILED bytes: %d ret: %02x" % (
+ len(bytesread), bytesread[0]))
+ return
+
+ if len(bytesread) % expected_bytes != 0:
+ debuglog("READ LINE WARNING: expected %d, got %d" % (
+ expected_bytes, len(bytesread)))
+
+ packet_count = len(bytesread) / expected_bytes
+
+ for i in range(0, packet_count):
+ start = i * expected_bytes
+ end = (i + 1) * expected_bytes
+ self.interpret_line(bytesread[start:end])
+
+ def interpret_line(self, data):
+ """Interpret a power record from INAs
+
+ Args:
+ data: one single record of bytes.
+
+ Output:
+ stdout of the record in csv format.
+ """
+ status, size = struct.unpack("<BB", data[0:2])
+ if len(data) != self.report_size(size):
+ print "READ LINE FAILED st:%d size:%d expected:%d len:%d" % (
+ status, size, self.report_size(size), len(data))
+ else:
+ pass
+
+ timestamp = struct.unpack("<Q", data[2:10])[0]
+ debuglog("READ LINE: st:%d size:%d time:%d" % (status, size, timestamp))
+ ftimestamp = float(timestamp) / 1000000.
+
+ output = "%f" % ftimestamp
+
+ for i in range(0, size):
+ idx = self.report_header_size() + 2*i
+ raw_w = struct.unpack("<H", data[idx:idx+2])[0]
+ uw = raw_w * self._inas[i]['uWscale']
+ name = self._inas[i]['name']
+ debuglog("READ %d %s: %fs: %fmW" % (i, name, ftimestamp, uw))
+ output += ", %.02f" % uw
+
+ logoutput(output)
+ return status
+
+ def load_board(self, brdfile):
+ """Load a board config.
+
+ Args:
+ brdfile: Filename of a json file decribing the INA wiring of this board.
+ """
+ with open(brdfile) as data_file:
+ data = json.load(data_file)
+
+ #TODO: validate this.
+ self._brdcfg = data;
+ if debug:
+ pprint(data)
+
+
+
+def main():
+ # Command line argument description.
+ parser = argparse.ArgumentParser(
+ description="Gather CSV data from sweetberry")
+ parser.add_argument('-b', '--board', type=str,
+ help="Board configuration file, eg. my.board", default="")
+ parser.add_argument('-c', '--config', type=str,
+ help="Rail config to monitor, eg my.config", default="")
+ parser.add_argument('-A', '--serial', type=str,
+ help="Serial number of sweetberry A", default="")
+ parser.add_argument('-B', '--serial_b', type=str,
+ help="Serial number of sweetberry B", default="")
+ parser.add_argument('-t', '--integration_us', type=int,
+ help="Target integration time for samples", default=100000)
+ parser.add_argument('-n', '--samples', type=int,
+ help="Samples to capture, or none to sample forever.", default=0)
+ parser.add_argument('-s', '--seconds', type=float,
+ help="Seconds to run capture. Overrides -n", default=0.)
+ parser.add_argument('--date', default=False,
+ help="Sync logged timestamp to host date", action="store_true")
+ parser.add_argument('--slow', default=False,
+ help="Intentionally overflow", action="store_true")
+ parser.add_argument('-v', '--verbose', default=False,
+ help="Very chatty printout", action="store_true")
+
+ args = parser.parse_args()
+
+ global debug
+ if args.verbose:
+ debug = True
+
+ integration_us = args.integration_us
+ if not args.board:
+ raise Exception("Power", "No board file selected, see board.README")
+ if not args.config:
+ raise Exception("Power", "No config file selected, see board.README")
+
+ brdfile = args.board
+ cfgfile = args.config
+ samples = args.samples
+ seconds = args.seconds
+ serial_a = args.serial
+ sync_date = args.date
+
+ sync_speed = .8
+ if args.slow:
+ sync_speed = 1.2
+
+ forever = True
+ if samples > 0 or seconds > 0.:
+ forever = False
+
+ with open(cfgfile) as data_file:
+ names = json.load(data_file)
+
+ p = Spower(serialname=serial_a)
+ p.load_board(brdfile)
+ p.reset()
+
+ for name in names:
+ p.add_ina_name(name)
+
+ if sync_date:
+ p.set_time(time.time() * 1000000)
+ else:
+ p.set_time(0)
+
+
+ # We will get back the actual integration us.
+ integration_us = p.start(integration_us)
+
+ if not seconds > 0.:
+ seconds = samples * integration_us / 1000000.;
+ end_time = time.time() + seconds
+ try:
+ while forever or end_time > time.time():
+ if (integration_us > 5000):
+ time.sleep((integration_us / 1000000.) * sync_speed)
+ ret = p.read_line()
+ finally:
+ p.stop()
+
+if __name__ == "__main__":
+ main()
+
+