diff options
Diffstat (limited to 'extra')
-rw-r--r-- | extra/usb_power/board.README | 71 | ||||
-rw-r--r-- | extra/usb_power/board/kevin/kevin.board | 18 | ||||
-rw-r--r-- | extra/usb_power/board/kevin/kevin_all.scenario | 18 | ||||
-rw-r--r-- | extra/usb_power/board/marlin/marlin.board | 74 | ||||
-rw-r--r-- | extra/usb_power/board/marlin/marlin_all_A.scenario | 42 | ||||
-rw-r--r-- | extra/usb_power/board/marlin/marlin_all_B.scenario | 28 | ||||
-rw-r--r-- | extra/usb_power/board/marlin/marlin_common.scenario | 1 | ||||
-rw-r--r-- | extra/usb_power/board/marlin/marlin_short.scenario | 1 | ||||
-rw-r--r-- | extra/usb_power/board/marlin/marlin_vbat.scenario | 1 | ||||
-rwxr-xr-x | extra/usb_power/powerlog.py | 549 |
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() + + |