tools: Basic ADC plotting tool
Change-Id: I29fed9ab26864455489cd0cda34cc9f9a1752e20
Reviewed-on: https://pigweed-review.googlesource.com/c/gonk/+/209092
Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
Pigweed-Auto-Submit: Anthony DiGirolamo <tonymd@google.com>
Lint: Lint 🤖 <android-build-ayeaye@system.gserviceaccount.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed-service-accounts.iam.gserviceaccount.com>
Reviewed-by: Eric Holland <hollande@google.com>
diff --git a/README.md b/README.md
index a4f2e7b..9e66092 100644
--- a/README.md
+++ b/README.md
@@ -216,3 +216,43 @@
| FPGA_IO_SPARE_2_1 | | 48 | PA6 | | |
| FPGA_IO_SPARE_2_2 | | 47 | PA5 | | |
| FPGA_IO_SPARE_2_3 | | 45 | PA4 | | |
+
+# Capture ADC samples and plot
+
+1. First bootstrap, run `pw build` and flash gonk using DFU.
+
+ ```sh
+ . ./bootstrap.sh
+ ```
+
+ ```sh
+ pw build
+ ```
+
+ Unplug gonk from USB and replug with MODE button held down.
+
+ Run `gonk-flash` on the MCU binary. This uses `pyfu-usb`.
+
+ ```sh
+ gonk-flash ./out/gn/arduino_size_optimized/obj/applications/fpga_config/fpga_config.bin
+ ```
+
+2. Start capturing ADC samples with:
+
+ ```sh
+ python tools/gonk_tools/write_fpga.py \
+ --bitstream-file ./out/gn/obj/fpga/toplevel/toplevel.bin \
+ --database ./out/gn/arduino_size_optimized/obj/applications/fpga_config/bin/fpga_config.elf \
+ --host-logfile gonk-host-logs.txt \
+ --device-logfile gonk-device-logs.txt \
+ --log-to-stderr
+ ```
+
+ - Press `Enter` to stop or start ADC continuous updates. It will begin automatically on startup.
+ - Press `ctrl-c` to quit.
+
+3. Plot the logs from `gonk-device-logs.txt` with:
+
+ ```sh
+ python tools/gonk_tools/plot.py -i gonk-device-logs.txt -o plot.svg
+ ```
diff --git a/tools/BUILD.gn b/tools/BUILD.gn
index dbf78b8..762fe97 100644
--- a/tools/BUILD.gn
+++ b/tools/BUILD.gn
@@ -30,6 +30,7 @@
"gonk_tools/firmware_files.py",
"gonk_tools/flash.py",
"gonk_tools/gonk_log_stream.py",
+ "gonk_tools/plot.py",
"gonk_tools/presubmit_checks.py",
"gonk_tools/write_fpga.py",
]
diff --git a/tools/gonk_tools/plot.py b/tools/gonk_tools/plot.py
new file mode 100644
index 0000000..0c38ca2
--- /dev/null
+++ b/tools/gonk_tools/plot.py
@@ -0,0 +1,139 @@
+# Copyright 2024 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""Plot ADC updates as an SVG from a device logfile."""
+
+import argparse
+from datetime import datetime
+from datetime import timedelta
+import logging
+from pathlib import Path
+import sys
+from typing import Optional
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+
+_LOG = logging.getLogger(__package__)
+ADC_COUNT = 5
+
+
+def _parse_args():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument(
+ '-i',
+ '--input-text',
+ type=Path,
+ default=Path('gonk-device-logs.txt'),
+ help='Input log text file.',
+ )
+ parser.add_argument(
+ '-o',
+ '--output-svg',
+ type=Path,
+ default=Path('plot.svg'),
+ help='Output svg file.',
+ )
+ return parser.parse_args()
+
+
+def main(
+ input_text: Path,
+ output_svg: Path,
+) -> int:
+ """Plot ADC values."""
+ # pylint: disable=too-many-locals
+
+ start_time: Optional[datetime] = None
+ time_values = []
+ vbus_values: list[list[int]] = []
+ vshunt_values: list[list[int]] = []
+ for i in range(ADC_COUNT):
+ vbus_values.append([])
+ vshunt_values.append([])
+
+ with input_text.open() as f:
+ current_time = datetime.now()
+
+ for line in f.readlines():
+ # Skip lines that are not ADC updates.
+ if 'delta_microseconds' not in line:
+ continue
+
+ parts = line.split()
+
+ # Extract the host timestamp
+ dtstr = parts[5] + ' ' + parts[6]
+ dt = datetime.strptime(dtstr, '%Y%m%d %H:%M:%S.%f')
+ # Extract delta_micros
+ delta_micros = int(parts[10])
+
+ # Set start timestamp if not found already.
+ if not start_time:
+ start_time = dt
+ current_time = start_time - timedelta(microseconds=delta_micros)
+
+ # Increment delta_micros
+ current_time += timedelta(microseconds=delta_micros)
+
+ # Save the timestamp and vbus vshunt values for plotting.
+ time_values.append((current_time - start_time).total_seconds())
+ vbus = list(int(i) for i in parts[12].split(','))
+ vshunt = list(int(i) for i in parts[14].split(','))
+
+ for i in range(ADC_COUNT):
+ vbus_values[i].append(vbus[i])
+ vshunt_values[i].append(vshunt[i])
+
+ # Plot vbus and vshunt values.
+ _fig, (ax1, ax2) = plt.subplots(
+ 2, 1, layout='constrained', figsize=[11.67, 8.27]
+ )
+
+ times = np.asarray(time_values)
+ ax1.set_xlabel('Time (s)')
+ ax2.set_xlabel('Time (s)')
+
+ linewidth = 0.7
+ ax1.set_ylabel('vbus')
+ for i in range(ADC_COUNT):
+ ax1.plot(
+ times,
+ np.asarray(vbus_values[i]),
+ label=f'vbus-{i}',
+ linestyle='dotted',
+ linewidth=linewidth,
+ )
+
+ ax2.set_ylabel('vshunt')
+ for i in range(ADC_COUNT):
+ ax2.plot(
+ times,
+ np.asarray(vshunt_values[i]),
+ label=f'vshunt-{i}',
+ linestyle='dotted',
+ linewidth=linewidth,
+ )
+
+ ax1.legend()
+ ax1.grid(True)
+ ax2.legend()
+ ax2.grid(True)
+
+ plt.savefig(output_svg)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(**vars(_parse_args())))
diff --git a/tools/setup.cfg b/tools/setup.cfg
index 9aef744..2263b55 100644
--- a/tools/setup.cfg
+++ b/tools/setup.cfg
@@ -22,6 +22,8 @@
packages = find:
zip_safe = False
install_requires =
+ matplotlib
+ numpy
pyfu-usb
pyserial