embeddedci 2.13.2__tar.gz → 2.13.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {embeddedci-2.13.2 → embeddedci-2.13.4}/CHANGELOG.md +24 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/PKG-INFO +88 -13
- {embeddedci-2.13.2 → embeddedci-2.13.4}/README.md +87 -12
- {embeddedci-2.13.2 → embeddedci-2.13.4}/pyproject.toml +1 -1
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/__init__.py +16 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/capabilities.py +27 -1
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/capture.py +3 -2
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/client.py +176 -16
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/constants.py +14 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/results.py +17 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/state.py +132 -6
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/transport/cloud.py +2 -1
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/wiring.py +1 -1
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/api_surface.json +76 -5
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/e2e/test_e2e_pod.py +29 -0
- embeddedci-2.13.4/tests/test_adc_range.py +218 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_client_v2.py +62 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_current_out_waveforms.py +3 -3
- {embeddedci-2.13.2 → embeddedci-2.13.4}/.gitignore +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/LICENSE +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/docs/event-uart-design.md +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/examples/README.md +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/examples/test_bmp280.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/__init__.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/can.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/ci.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/cli_config.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/cli_login.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/cloud_auth.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/connection.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/control_loop.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/decode.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/discovery.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/dsp.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/errors.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/flash.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/gpio.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/gps.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/i2c.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/lease.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/lowlevel.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/motor_emulator.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/power.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/protocol.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/pytest_plugin.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/replay.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/sensor.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/server_api.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/spi.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/transport/__init__.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/transport/base.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/transport/serial.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/transport/tcp.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/transport/tls.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/transport/usblink.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/uart.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/benchpod/waveforms.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/py.typed +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/src/embeddedci/upload_build.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/conftest.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/e2e/README.md +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/e2e/conftest.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/e2e/e2e_helpers.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/e2e/test_e2e_cloud.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/e2e/test_e2e_digital.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/e2e/test_e2e_dut.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/e2e/test_e2e_gpio.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/e2e/test_e2e_la13_14.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/e2e/test_e2e_rev3.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/e2e/test_e2e_spi.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/e2e/test_e2e_usb.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/examples/test_bmp280_hil.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/examples/test_can_ecu_demo.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/examples/test_can_loopback_hil.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/examples/test_smoke.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_adc_fft_hw.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_api_surface.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_can.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_capabilities.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_capabilities_scope.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_capture.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_capture_replay_hw.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_ci.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_cloud.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_connection.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_control_loop.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_control_loop_hw.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_dac_limits_fixture.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_dac_path_default.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_dac_path_ranges.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_decode.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_decode_parity.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_docs_coverage.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_dsp.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_flash_bridge.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_flash_build.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_generate16.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_gpio.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_i2c_decode.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_i2c_parity.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_image_switching.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_integration.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_la_voltage_fixture.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_lan_tls.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_lease.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_motor_emulator.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_one_source.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_pins_bias.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_protocol.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_refusal_errors.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_replay.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_sensor.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_serial_autodetect.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_serial_text_console.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_spi.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_tcp_transport.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_timed_power.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_timing.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_transport_failures.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_trigger_power.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_uart_capture.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_uart_session.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_unsupported_feature.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_upload_build.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_usb_net.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_waveforms.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/test_wiring.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/testdata/i2c_decode_vectors.json +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/testdata/spi_decode_vectors.json +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.4}/tests/testdata/uart_decode_vectors.json +0 -0
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog — `embeddedci`
|
|
2
2
|
|
|
3
|
+
## 2.13.4
|
|
4
|
+
|
|
5
|
+
- The ADC input range (pod firmware 3.9.5+, analog module v3r4, `Capabilities.adc_low_range`):
|
|
6
|
+
`BenchPod.adc_range(range=None)` reads or switches it (`"normal"` ±32.8 V, `"low"` ±5.46 V at
|
|
7
|
+
about 6x the resolution), `adc_range_test()` self-tests it through the cal1 loopback, and
|
|
8
|
+
`analog_path`, `adc_read`, `capture_adc` take `range=`. `calibrate_adc_gain("cal1", range="low")`
|
|
9
|
+
measures the low range's gain (`Calibration.adc_low_gain_ppm`). Captures scale with the fit of
|
|
10
|
+
the range in use: the SDK re-reads it whenever a reply says the pod changed range.
|
|
11
|
+
`AdcReading` gains `range`, `fullscale`, `overrange`; `Capture` gains `range`, `overrange`,
|
|
12
|
+
`clipped_samples`; `AnalogPathState.range`; `Capabilities.adc_range`, `adc_range_fullscale`.
|
|
13
|
+
New types `AdcRange`, `AdcRangeState`, `AdcRangeTest`, `AdcRangeTestPoint`.
|
|
14
|
+
- `capture_adc(source="current_in")` reads the pod's `current_in` fit after routing, so it is the
|
|
15
|
+
fit of the range in use.
|
|
16
|
+
|
|
17
|
+
## 2.13.3
|
|
18
|
+
|
|
19
|
+
- The DMM-guided calibration (pod firmware 3.9.4+, `Capabilities.cal_dmm`):
|
|
20
|
+
`BenchPod.calibrate_dac(path, points)` stores this pod's own line for a DAC output from two
|
|
21
|
+
`(code16, volts)` DMM readings, `calibrate_adc_gain(source)` measures an ADC gain (`cal1`,
|
|
22
|
+
`cal2`, `ext`) through a loopback, and `clear_calibration(part)` clears one part (`offset`,
|
|
23
|
+
`dac`, `adc`). `Calibration` gains `dac` (`DacLine`), `adc_gain_ppm`, `dmm` and `measured`;
|
|
24
|
+
`BenchPod.DMM_SET_POINTS` has the set points. New types `DacCalPath`, `AdcGainSource`,
|
|
25
|
+
`CalibrationPart`. `Calibration.zero_count` is the raw J8-open zero the pod derives its offset from.
|
|
26
|
+
|
|
3
27
|
## 2.13.2
|
|
4
28
|
|
|
5
29
|
- Docs: `calibrate()` corrects every ADC source from firmware 3.9.3 (they share the front end) and
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: embeddedci
|
|
3
|
-
Version: 2.13.
|
|
3
|
+
Version: 2.13.4
|
|
4
4
|
Summary: BenchPod SDK and pytest plugin for EmbeddedCI hardware-in-the-loop testing
|
|
5
5
|
Project-URL: Homepage, https://embeddedci.com
|
|
6
6
|
Project-URL: Repository, https://github.com/embeddedci-com/embeddedci-python
|
|
@@ -1021,6 +1021,40 @@ microvolt resolution (about 76 µV a step on `"5v"`) and `code16` is the 16-bit
|
|
|
1021
1021
|
stays the nearest 8-bit level. On older firmware `code16` is `None` and the steps are 8-bit. `adc_read` routes the source (`"ext"`, `"cal1"`, `"cal2"`, `"current_in"`) and returns one averaged,
|
|
1022
1022
|
calibrated reading; the pod refuses it while the input is still moving (e.g. a DAC left running).
|
|
1023
1023
|
|
|
1024
|
+
### The ADC input range (analog module v3r4)
|
|
1025
|
+
|
|
1026
|
+
Analog module v3r4 (firmware 3.9.5+, `Capabilities.adc_low_range`) adds a ÷2 low range to the
|
|
1027
|
+
ADC input next to the normal ÷12 one:
|
|
1028
|
+
|
|
1029
|
+
| Range | Full scale | Resolution | Input impedance |
|
|
1030
|
+
|---|---|---|---|
|
|
1031
|
+
| `"normal"` (default) | ±32.8 V | ~1 mV per count | ~1 MΩ |
|
|
1032
|
+
| `"low"` | ±5.46 V | ~167 µV per count | 1.82 MΩ: use 1x leads, not 10x probes |
|
|
1033
|
+
|
|
1034
|
+
```python
|
|
1035
|
+
st = bp.adc_range("low") # AdcRangeState(range, supported, fullscale, ...)
|
|
1036
|
+
r = bp.adc_read("ext") # read in the low range: r.range == "low"
|
|
1037
|
+
r = bp.adc_read("current_in", range="low") # or switch it with the reading
|
|
1038
|
+
cap = bp.capture_adc(4096, source="ext", range="low")
|
|
1039
|
+
print(cap.range, cap.overrange) # a clipped sample: capture again in the normal range
|
|
1040
|
+
bp.adc_range("normal")
|
|
1041
|
+
print(bp.adc_range_test().passed) # self-test through the cal1 loopback (5 V output)
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
The range applies to `ext`, `cal1` and `current_in` (4-20 mA fits inside it); `cal2` has none.
|
|
1045
|
+
It stays until it is changed, `analog_path("off")` or `analog_path("cal2")`, or the pod restarts.
|
|
1046
|
+
`analog_path`, `adc_read` and `capture_adc` take `range=` too. Captures are raw counts: the SDK
|
|
1047
|
+
keeps the fit it scales them with on the range in use (`Capabilities.adc_range`), and re-reads it
|
|
1048
|
+
whenever a reply says the pod moved to the other range. `AdcReading.overrange` and
|
|
1049
|
+
`Capture.overrange` (with `clipped_samples`) flag samples on the ADC's rails. A board without the
|
|
1050
|
+
low range raises `BenchPodError` for `"low"`.
|
|
1051
|
+
|
|
1052
|
+
`adc_range_test()` drives the 5 V output into `cal1` at 1 V and 3.5 V, reads each point in both
|
|
1053
|
+
ranges, and fails (`passed` is `False`) when they disagree by more than 5 % + 30 mV. The low range's
|
|
1054
|
+
gain is calibrated through `cal1` with `calibrate_adc_gain("cal1", range="low")` (after the offset and
|
|
1055
|
+
the 5 V DMM line, see below); it serves every source in the low range and shows as
|
|
1056
|
+
`Calibration.adc_low_gain_ppm`.
|
|
1057
|
+
|
|
1024
1058
|
### Calibrating the ADC offset
|
|
1025
1059
|
|
|
1026
1060
|
`"current_in"` reads the 4-20 mA terminal (J8) across a 249 Ω resistor, so 4 mA is 0.996 V and 20 mA is
|
|
@@ -1046,8 +1080,45 @@ and keeps what it had. `a` and `b` are the fit the pod now uses for `current_in`
|
|
|
1046
1080
|
Every ADC source shares the same front end after its input switch, so from firmware 3.9.3 the pod
|
|
1047
1081
|
takes the offset out of all of them: `adc_read` on `ext`, `cal1` and `cal2`, and `capture_adc`,
|
|
1048
1082
|
whose `volts` use the fit the pod reports (on the LAN in `status`, through embeddedci.com in its
|
|
1049
|
-
capabilities). Older firmware corrects `current_in` only.
|
|
1050
|
-
|
|
1083
|
+
capabilities). Older firmware corrects `current_in` only. Needs firmware 3.4.0 (capability
|
|
1084
|
+
`calibrate`, `Capabilities.calibrate`). The web app has it in the pod's Settings tab, under
|
|
1085
|
+
Calibration.
|
|
1086
|
+
|
|
1087
|
+
#### Calibrating with a DMM
|
|
1088
|
+
|
|
1089
|
+
With a bench DMM the pod can also take out what the offset leaves (firmware 3.9.4+, capability
|
|
1090
|
+
`cal_dmm`, `Capabilities.cal_dmm`): its own DAC line per output, then the ADC gain per source.
|
|
1091
|
+
Every step is optional and stored on its own; an output or source without one keeps the built-in
|
|
1092
|
+
fit. Disconnect the DUT from the DAC outputs first: this drives up to ±5 V on them.
|
|
1093
|
+
|
|
1094
|
+
```python
|
|
1095
|
+
points = []
|
|
1096
|
+
for volts in bp.DMM_SET_POINTS["5v"]: # 1.0 and 4.0 V; 3v3: 0.5 / 3.0, 12v: -5 / +5
|
|
1097
|
+
out = bp.dac_output("5v", volts=volts)
|
|
1098
|
+
points.append((out.code16, read_dmm())) # the DMM on the 5 V output SMA, DC volts
|
|
1099
|
+
cal = bp.calibrate_dac("5v", points) # this pod's line for the 5 V output
|
|
1100
|
+
bp.dac_output("off")
|
|
1101
|
+
print(cal.dac["5v"]) # DacLine(a, b, offset=-0.0031, gain_ppm=-996)
|
|
1102
|
+
|
|
1103
|
+
bp.calibrate_adc_gain("cal1") # 5 V loopback, against the line just stored
|
|
1104
|
+
bp.calibrate_adc_gain("ext") # needs the 12v line and an SMA cable ±12 V -> ADC in
|
|
1105
|
+
bp.clear_calibration("dac") # one part: "offset", "dac" or "adc"; no part = all
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
`calibrate_dac(path, points)` takes two `(code16, volts)` pairs: the 16-bit code `dac_output`
|
|
1109
|
+
drove and the DMM reading. From then on `dac_output`, the calibrated `generate` and the DAC output
|
|
1110
|
+
limits convert with this pod's line (`DacLine`: `a`, `b` per 16-bit code, `offset`, `gain_ppm`
|
|
1111
|
+
against the built-in line). `calibrate_adc_gain(source)` measures `cal1` through the 5 V output,
|
|
1112
|
+
`cal2` through the ±12 V output and `ext` (the front SMA, which `capture_adc` and the control loop
|
|
1113
|
+
use) through an SMA cable from the ±12 V output to the ADC input; `current_in` follows `cal1`. It
|
|
1114
|
+
needs the ADC offset and the driving output's DMM line, and `Calibration.measured` says what it
|
|
1115
|
+
saw. The pod refuses (`BenchPodError`) and keeps what it had when a reading cannot be right: two
|
|
1116
|
+
codes too close, a reading far from what the output should give (the DMM on another SMA), a gain
|
|
1117
|
+
more than 2 % off. It also refuses while DAC output limits are set. `Calibration.dac`,
|
|
1118
|
+
`Calibration.adc_gain_ppm` and `Calibration.dmm` show what is stored. The pod stores raw
|
|
1119
|
+
measurements (the J8-open zero as `Calibration.zero_count`, the DMM points) and derives every fit
|
|
1120
|
+
from them, so a later firmware with re-measured built-in fits keeps them right. Going back to
|
|
1121
|
+
firmware 3.4.0 to 3.9.3 keeps the offset; the DMM parts are not used there.
|
|
1051
1122
|
|
|
1052
1123
|
### The 4-20 mA output
|
|
1053
1124
|
|
|
@@ -1140,9 +1211,9 @@ both = bp.capture_correlated(adc_samples=4096, adc_sample_rate_hz=400_000,
|
|
|
1140
1211
|
print(both.adc.duration, both.la.duration) # one hardware trigger: timebases align
|
|
1141
1212
|
```
|
|
1142
1213
|
|
|
1143
|
-
* `capture_adc(samples=4096, *, sample_rate_hz=None, source=None, trigger=None, trigger_timeout=10.0)` — omitted rate = the device
|
|
1214
|
+
* `capture_adc(samples=4096, *, sample_rate_hz=None, source=None, trigger=None, trigger_timeout=10.0, range=None)` — omitted rate = the device
|
|
1144
1215
|
maximum; the **achieved** rate is on the result. `source` routes the ADC first (omitted, the
|
|
1145
|
-
current routing is left alone). `volts` use the front-SMA calibration; for the other sources
|
|
1216
|
+
current routing is left alone); `range` sets the [ADC input range](#the-adc-input-range-analog-module-v3r4). `volts` use the front-SMA calibration; for the other sources
|
|
1146
1217
|
compare `counts` or use `adc_read`.
|
|
1147
1218
|
* `capture_la(samples=4096, *, sample_rate_hz=None, stop_dac_after=None, trigger=None, trigger_timeout=10.0)` — 12-bit words; bit *n* is
|
|
1148
1219
|
channel LA*n+1*. The **achieved** rate is on the result and can be below the request: the pod
|
|
@@ -1631,7 +1702,7 @@ lease_wait=600.0, lease_ttl=120, wiring=None)`. A context manager; `close()` is
|
|
|
1631
1702
|
| GPIO and pins | `gpio(la, mode="output", *, level=None)`, `gpio_pins(las, mode="output", *, level=None)`, `configure_gpio(las, mode="output", *, level=None)`, `set_gpio(la, level)`, `read_gpio(la)`, `wait_for_level(la, level, *, timeout, poll=0.005)` (`False` when `timeout` passes first), `pin_levels()`, `la_pins()`, `release_gpio(*las)`, `la_step(la, *, steps, delay, dir_la=None, direction=0)` |
|
|
1632
1703
|
| I2C sensor | `enable_i2c_sensor(sensor=Sensor.BMP280, *, sda=None, scl=None, address=None, **values)`, `set_i2c_sensor(**values)`, `disable_i2c_sensor()`, `i2c_sensor_status()`, `i2c_sensor_types()`, `i2c_sensor_regs()`, `i2c_sensor_capture(...)` |
|
|
1633
1704
|
| GPS receiver | `enable_gps(tx, *, baud=9600, rate_hz=1, sentences=None, utc=None, **fix)`, `set_gps(*, utc=None, **fix)`, `disable_gps()`, `gps_status()` |
|
|
1634
|
-
| Analog | `analog_path(path)`, `adc_read(source="ext")`, `dac_output(path, *, volts=None)`, `current_out(current)`, `current_out_range()`, `calibrate(source="current_in")`, `calibration()`, `clear_calibration()` |
|
|
1705
|
+
| Analog | `analog_path(path, *, range=None)`, `adc_read(source="ext", *, range=None)`, `adc_range(range=None)`, `adc_range_test()`, `dac_output(path, *, volts=None)`, `current_out(current)`, `current_out_range()`, `calibrate(source="current_in")`, `calibration()`, `clear_calibration(part=None)`, `calibrate_dac(path, points)`, `calibrate_adc_gain(source, *, range="normal")`, `DMM_SET_POINTS` |
|
|
1635
1706
|
| Captures | `capture_adc(...)`, `capture_la(...)`, `capture_correlated(...)`, `decode(source, protocol="i2c", ...)` |
|
|
1636
1707
|
| DAC | `generate(...)`, `replay(...)`, `replay_waveform(...)`, `dac_stop()`, `control_loop(...)`, `loop_input(...)`, `loop_probe()` |
|
|
1637
1708
|
| CAN | `open_can(*, bitrate=500000, mode="normal", term=False, fd=False)`, `can_config(...)`, `can_disable()`, `can_status()`, `can_term(on)`, `can_write(...)`, `can_read(...)`, `can_respond(...)`, `can_respond_clear()` |
|
|
@@ -1672,12 +1743,16 @@ few built from other shapes); `to_dict()` goes the other way where a type is sen
|
|
|
1672
1743
|
| `I2CByte` | `I2CMessage.data` | `value`, `ack` |
|
|
1673
1744
|
| `UartFrame` | `decode("uart")` | `index`, `start_us`, `end_us`, `value`, `hex`, `text`, `ok`, `error` |
|
|
1674
1745
|
| `SpiFrame` | `decode("spi")` | `index`, `start_us`, `end_us`, `mosi`, `miso`, `mosi_hex`, `miso_hex` |
|
|
1675
|
-
| `AnalogPathState` | `analog_path` | `path`, `dac_mux_register`, `cal_relay_register` |
|
|
1676
|
-
| `AdcReading` | `adc_read` | `source`, `voltage`, `count`, `span`, `offset`, `current` |
|
|
1746
|
+
| `AnalogPathState` | `analog_path` | `path`, `dac_mux_register`, `cal_relay_register`, `range` |
|
|
1747
|
+
| `AdcReading` | `adc_read` | `source`, `voltage`, `count`, `span`, `offset`, `current`, `range`, `fullscale`, `overrange` |
|
|
1748
|
+
| `AdcRangeState` | `adc_range` | `range`, `supported`, `fullscale`, `normal_fullscale`, `low_fullscale`, `a`, `b` (volts, the `ext` fit of the range) |
|
|
1749
|
+
| `AdcRangeTest` | `adc_range_test` | `passed`, `points` (each an `AdcRangeTestPoint`: `set_voltage`, `normal`, `low`, `ok`) |
|
|
1750
|
+
| `AdcRange` (literal) | `adc_range`, `range=` | `"normal"`, `"low"` |
|
|
1677
1751
|
| `DacOutput` | `dac_output` | `path`, `voltage`, `code` |
|
|
1678
1752
|
| `CurrentOutput` | `current_out`, `current_out_range` | `current`, `code`, `min_current`, `max_current` |
|
|
1679
|
-
| `Calibration` | `calibrate`, `calibration`, `clear_calibration` | `source`, `calibrated`, `offset`, `a`, `b`, `count`, `span`, `samples` |
|
|
1680
|
-
| `
|
|
1753
|
+
| `Calibration` | `calibrate`, `calibration`, `clear_calibration`, `calibrate_dac`, `calibrate_adc_gain` | `source`, `calibrated`, `offset`, `a`, `b`, `count`, `span`, `samples`, `dac`, `adc_gain_ppm`, `adc_low_gain_ppm`, `dmm`, `measured`, `zero_count` |
|
|
1754
|
+
| `DacLine` | `Calibration.dac` | `a`, `b`, `offset`, `gain_ppm` |
|
|
1755
|
+
| `Capture` | `capture_adc` | `counts`, `volts`, `currents`, `sample_rate_hz`, `source`, `trigger`, `range`, `overrange`, `clipped_samples`, `duration`, `times()`, `mean()`, `min()`, `max()`, `rms()`, `rms_ac()`, `peak_to_peak()`, `first_crossing()`, `crossing_times()`, `fft()`, `dominant_frequency()` |
|
|
1681
1756
|
| `LaCapture` | `capture_la` | `words`, `channels`, `sample_rate_hz`, `trigger`, `duration`, `channel(la)`, `level_at()`, `edges()`, `edge_times()`, `first_edge()`, `delay()`, `frequency()`, `duty_cycle()`, `pulse_widths()`, `decode()` |
|
|
1682
1757
|
| `CorrelatedCapture` | `capture_correlated` | `adc` (a `Capture`), `la` (a `LaCapture`), sharing one hardware trigger |
|
|
1683
1758
|
| `Trigger` | `capture_adc`, `capture_la`, `capture_correlated` | `la`, `edge` |
|
|
@@ -1718,9 +1793,9 @@ reported keeps its default (`False`, `0` or `""`), so a feature check is just
|
|
|
1718
1793
|
| Group | Fields |
|
|
1719
1794
|
|---|---|
|
|
1720
1795
|
| Identity | `board`, `firmware_version`, `board_rev` (`"v2"`, `"v3"`, `"unknown"`, or `""`), `la_vccio_mv` (the LA bank voltage the pod reports) |
|
|
1721
|
-
| ADC | `adc_bits`, `adc_fullscale_mv`, `adc_channels`, `adc_offset_counts`, `adc_affine` (the front-end fit), `adc_max_count`, `counts_to_volts(count)` |
|
|
1796
|
+
| ADC | `adc_bits`, `adc_fullscale_mv`, `adc_channels`, `adc_offset_counts`, `adc_affine` (the front-end fit, of the range in use), `adc_low_range` (analog module v3r4+: the low range), `adc_range` (`"normal"`, `"low"`, or `""` before firmware 3.9.5), `adc_range_fullscale` (volts, ±), `adc_max_count`, `counts_to_volts(count)` |
|
|
1722
1797
|
| DAC and replay | `dac`, `dac_dc`, `dac_replay`, `dac_deep_replay`, `dac_control_loop`, `dac_loop_sources` (gateware v29+), `dac_loop_input_map` (v30+), `dac_cotrig` (v27+), `generate16` (firmware 3.9.1+: `generate` in calibrated 16-bit volts), `dac_bits`, `dac_replay_bits`, `dac_replay_max_samples`, `dac_fullscale_mv`, `dac_channels`, `dac_limits` (output limits on the DAC paths) |
|
|
1723
|
-
| Features | `scope`, `analyzer`, `serial`, `tunnel`, `command`, `ota`, `la_pins`, `gpio_read` (direct pin reads, v35+), `capture_trigger` (v35+), `power_profile`, `capture_b64` (faster ADC read-back, used automatically), `nrst_pin` (the dedicated reset pin, rev3), `usb_cc` (rev3), `spi_master` (v45+), `spi_stream`, `calibrate`, `current_out`, `can`, `pod_current` (`PowerStatus.pod`), `sensor_types` (the BME280/SHT4x/MPU-6050 models), `gps` (the GPS receiver, gateware v48+), `analog` (`False` on the digital-only board, `None` when not announced) |
|
|
1798
|
+
| Features | `scope`, `analyzer`, `serial`, `tunnel`, `command`, `ota`, `la_pins`, `gpio_read` (direct pin reads, v35+), `capture_trigger` (v35+), `power_profile`, `capture_b64` (faster ADC read-back, used automatically), `nrst_pin` (the dedicated reset pin, rev3), `usb_cc` (rev3), `spi_master` (v45+), `spi_stream`, `calibrate`, `cal_dmm` (firmware 3.9.4+: `calibrate_dac`, `calibrate_adc_gain`), `current_out`, `can`, `pod_current` (`PowerStatus.pod`), `sensor_types` (the BME280/SHT4x/MPU-6050 models), `gps` (the GPS receiver, gateware v48+), `analog` (`False` on the digital-only board, `None` when not announced) |
|
|
1724
1799
|
| Firmware updates and policies | `flash_kb` (MCU flash in KiB, 0 when not reported), `blob_slots`, `ota_sig`, `sig_policy` (`"audit"`, `"required"`, ...), `sig_keys`, `sig_policy_cmd`, `lan_policy` (`"open"`, `"locked"`, `"off"`), `lan_policy_cmd`, `tunnel_max_tier`, `lease_state` (the pod refuses LAN writes while a cloud job holds it), `cloud_ca`, `cloud_proxy`, `ws_auth_v2` (server only) |
|
|
1725
1800
|
| Boot health | `safe_mode`, `safe_reason`, `last_crash`, `reset_cause`, `boot_warning()` (a one-line warning after safe mode or a crash, else `None`) |
|
|
1726
1801
|
| Source | `raw` (the map it was parsed from) |
|
|
@@ -1761,7 +1836,7 @@ timeout=60.0, lease_id=None)`. Errors raise `ServerApiError` (`status`), and an
|
|
|
1761
1836
|
| `curve_output_at(curve, input_code)`, `input_percent_to_code(percent)` | evaluate a curve the way the gateware does; an input percentage as a code |
|
|
1762
1837
|
| `encode_curve_b64url(codes)` | 16-bit DAC codes as little-endian base64url, the wire form of a curve |
|
|
1763
1838
|
| `INTERNAL`, `EXTERNAL`, `PIN1` … `PIN14`, `BMP280_ADDR_PRIMARY`, `BMP280_ADDR_SECONDARY` | named constants (see [API conventions](#api-conventions-and-stability)) |
|
|
1764
|
-
| `DacPath`, `DacOutputPath`, `AnalogPath`, `AdcSource`, `CalibrateSource` (`"current_in"`), `LoopSource`, `Waveshape`, `ReplayMapping`, `DecodeProtocol`, `CanMode`, `FaultType`, `GpioMode`, `Edge`, `TriggerEdge` | the `Literal` string types |
|
|
1839
|
+
| `DacPath`, `DacOutputPath`, `AnalogPath`, `AdcSource`, `CalibrateSource` (`"current_in"`), `DacCalPath` (`"3v3"`, `"5v"`, `"12v"`), `AdcGainSource` (`"cal1"`, `"cal2"`, `"ext"`), `CalibrationPart` (`"offset"`, `"dac"`, `"adc"`), `LoopSource`, `Waveshape`, `ReplayMapping`, `DecodeProtocol`, `CanMode`, `FaultType`, `GpioMode`, `Edge`, `TriggerEdge` | the `Literal` string types |
|
|
1765
1840
|
| `decode`, `i2c`, `can`, `control_loop`, `motor_emulator`, `dsp` | submodules: off-device decoders, I2C helpers, CAN types, curve helpers, the emulator's registers, and `dsp`, a pure-Python mirror of the server's record-to-replay processing |
|
|
1766
1841
|
|
|
1767
1842
|
## Examples
|
|
@@ -983,6 +983,40 @@ microvolt resolution (about 76 µV a step on `"5v"`) and `code16` is the 16-bit
|
|
|
983
983
|
stays the nearest 8-bit level. On older firmware `code16` is `None` and the steps are 8-bit. `adc_read` routes the source (`"ext"`, `"cal1"`, `"cal2"`, `"current_in"`) and returns one averaged,
|
|
984
984
|
calibrated reading; the pod refuses it while the input is still moving (e.g. a DAC left running).
|
|
985
985
|
|
|
986
|
+
### The ADC input range (analog module v3r4)
|
|
987
|
+
|
|
988
|
+
Analog module v3r4 (firmware 3.9.5+, `Capabilities.adc_low_range`) adds a ÷2 low range to the
|
|
989
|
+
ADC input next to the normal ÷12 one:
|
|
990
|
+
|
|
991
|
+
| Range | Full scale | Resolution | Input impedance |
|
|
992
|
+
|---|---|---|---|
|
|
993
|
+
| `"normal"` (default) | ±32.8 V | ~1 mV per count | ~1 MΩ |
|
|
994
|
+
| `"low"` | ±5.46 V | ~167 µV per count | 1.82 MΩ: use 1x leads, not 10x probes |
|
|
995
|
+
|
|
996
|
+
```python
|
|
997
|
+
st = bp.adc_range("low") # AdcRangeState(range, supported, fullscale, ...)
|
|
998
|
+
r = bp.adc_read("ext") # read in the low range: r.range == "low"
|
|
999
|
+
r = bp.adc_read("current_in", range="low") # or switch it with the reading
|
|
1000
|
+
cap = bp.capture_adc(4096, source="ext", range="low")
|
|
1001
|
+
print(cap.range, cap.overrange) # a clipped sample: capture again in the normal range
|
|
1002
|
+
bp.adc_range("normal")
|
|
1003
|
+
print(bp.adc_range_test().passed) # self-test through the cal1 loopback (5 V output)
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
The range applies to `ext`, `cal1` and `current_in` (4-20 mA fits inside it); `cal2` has none.
|
|
1007
|
+
It stays until it is changed, `analog_path("off")` or `analog_path("cal2")`, or the pod restarts.
|
|
1008
|
+
`analog_path`, `adc_read` and `capture_adc` take `range=` too. Captures are raw counts: the SDK
|
|
1009
|
+
keeps the fit it scales them with on the range in use (`Capabilities.adc_range`), and re-reads it
|
|
1010
|
+
whenever a reply says the pod moved to the other range. `AdcReading.overrange` and
|
|
1011
|
+
`Capture.overrange` (with `clipped_samples`) flag samples on the ADC's rails. A board without the
|
|
1012
|
+
low range raises `BenchPodError` for `"low"`.
|
|
1013
|
+
|
|
1014
|
+
`adc_range_test()` drives the 5 V output into `cal1` at 1 V and 3.5 V, reads each point in both
|
|
1015
|
+
ranges, and fails (`passed` is `False`) when they disagree by more than 5 % + 30 mV. The low range's
|
|
1016
|
+
gain is calibrated through `cal1` with `calibrate_adc_gain("cal1", range="low")` (after the offset and
|
|
1017
|
+
the 5 V DMM line, see below); it serves every source in the low range and shows as
|
|
1018
|
+
`Calibration.adc_low_gain_ppm`.
|
|
1019
|
+
|
|
986
1020
|
### Calibrating the ADC offset
|
|
987
1021
|
|
|
988
1022
|
`"current_in"` reads the 4-20 mA terminal (J8) across a 249 Ω resistor, so 4 mA is 0.996 V and 20 mA is
|
|
@@ -1008,8 +1042,45 @@ and keeps what it had. `a` and `b` are the fit the pod now uses for `current_in`
|
|
|
1008
1042
|
Every ADC source shares the same front end after its input switch, so from firmware 3.9.3 the pod
|
|
1009
1043
|
takes the offset out of all of them: `adc_read` on `ext`, `cal1` and `cal2`, and `capture_adc`,
|
|
1010
1044
|
whose `volts` use the fit the pod reports (on the LAN in `status`, through embeddedci.com in its
|
|
1011
|
-
capabilities). Older firmware corrects `current_in` only.
|
|
1012
|
-
|
|
1045
|
+
capabilities). Older firmware corrects `current_in` only. Needs firmware 3.4.0 (capability
|
|
1046
|
+
`calibrate`, `Capabilities.calibrate`). The web app has it in the pod's Settings tab, under
|
|
1047
|
+
Calibration.
|
|
1048
|
+
|
|
1049
|
+
#### Calibrating with a DMM
|
|
1050
|
+
|
|
1051
|
+
With a bench DMM the pod can also take out what the offset leaves (firmware 3.9.4+, capability
|
|
1052
|
+
`cal_dmm`, `Capabilities.cal_dmm`): its own DAC line per output, then the ADC gain per source.
|
|
1053
|
+
Every step is optional and stored on its own; an output or source without one keeps the built-in
|
|
1054
|
+
fit. Disconnect the DUT from the DAC outputs first: this drives up to ±5 V on them.
|
|
1055
|
+
|
|
1056
|
+
```python
|
|
1057
|
+
points = []
|
|
1058
|
+
for volts in bp.DMM_SET_POINTS["5v"]: # 1.0 and 4.0 V; 3v3: 0.5 / 3.0, 12v: -5 / +5
|
|
1059
|
+
out = bp.dac_output("5v", volts=volts)
|
|
1060
|
+
points.append((out.code16, read_dmm())) # the DMM on the 5 V output SMA, DC volts
|
|
1061
|
+
cal = bp.calibrate_dac("5v", points) # this pod's line for the 5 V output
|
|
1062
|
+
bp.dac_output("off")
|
|
1063
|
+
print(cal.dac["5v"]) # DacLine(a, b, offset=-0.0031, gain_ppm=-996)
|
|
1064
|
+
|
|
1065
|
+
bp.calibrate_adc_gain("cal1") # 5 V loopback, against the line just stored
|
|
1066
|
+
bp.calibrate_adc_gain("ext") # needs the 12v line and an SMA cable ±12 V -> ADC in
|
|
1067
|
+
bp.clear_calibration("dac") # one part: "offset", "dac" or "adc"; no part = all
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
`calibrate_dac(path, points)` takes two `(code16, volts)` pairs: the 16-bit code `dac_output`
|
|
1071
|
+
drove and the DMM reading. From then on `dac_output`, the calibrated `generate` and the DAC output
|
|
1072
|
+
limits convert with this pod's line (`DacLine`: `a`, `b` per 16-bit code, `offset`, `gain_ppm`
|
|
1073
|
+
against the built-in line). `calibrate_adc_gain(source)` measures `cal1` through the 5 V output,
|
|
1074
|
+
`cal2` through the ±12 V output and `ext` (the front SMA, which `capture_adc` and the control loop
|
|
1075
|
+
use) through an SMA cable from the ±12 V output to the ADC input; `current_in` follows `cal1`. It
|
|
1076
|
+
needs the ADC offset and the driving output's DMM line, and `Calibration.measured` says what it
|
|
1077
|
+
saw. The pod refuses (`BenchPodError`) and keeps what it had when a reading cannot be right: two
|
|
1078
|
+
codes too close, a reading far from what the output should give (the DMM on another SMA), a gain
|
|
1079
|
+
more than 2 % off. It also refuses while DAC output limits are set. `Calibration.dac`,
|
|
1080
|
+
`Calibration.adc_gain_ppm` and `Calibration.dmm` show what is stored. The pod stores raw
|
|
1081
|
+
measurements (the J8-open zero as `Calibration.zero_count`, the DMM points) and derives every fit
|
|
1082
|
+
from them, so a later firmware with re-measured built-in fits keeps them right. Going back to
|
|
1083
|
+
firmware 3.4.0 to 3.9.3 keeps the offset; the DMM parts are not used there.
|
|
1013
1084
|
|
|
1014
1085
|
### The 4-20 mA output
|
|
1015
1086
|
|
|
@@ -1102,9 +1173,9 @@ both = bp.capture_correlated(adc_samples=4096, adc_sample_rate_hz=400_000,
|
|
|
1102
1173
|
print(both.adc.duration, both.la.duration) # one hardware trigger: timebases align
|
|
1103
1174
|
```
|
|
1104
1175
|
|
|
1105
|
-
* `capture_adc(samples=4096, *, sample_rate_hz=None, source=None, trigger=None, trigger_timeout=10.0)` — omitted rate = the device
|
|
1176
|
+
* `capture_adc(samples=4096, *, sample_rate_hz=None, source=None, trigger=None, trigger_timeout=10.0, range=None)` — omitted rate = the device
|
|
1106
1177
|
maximum; the **achieved** rate is on the result. `source` routes the ADC first (omitted, the
|
|
1107
|
-
current routing is left alone). `volts` use the front-SMA calibration; for the other sources
|
|
1178
|
+
current routing is left alone); `range` sets the [ADC input range](#the-adc-input-range-analog-module-v3r4). `volts` use the front-SMA calibration; for the other sources
|
|
1108
1179
|
compare `counts` or use `adc_read`.
|
|
1109
1180
|
* `capture_la(samples=4096, *, sample_rate_hz=None, stop_dac_after=None, trigger=None, trigger_timeout=10.0)` — 12-bit words; bit *n* is
|
|
1110
1181
|
channel LA*n+1*. The **achieved** rate is on the result and can be below the request: the pod
|
|
@@ -1593,7 +1664,7 @@ lease_wait=600.0, lease_ttl=120, wiring=None)`. A context manager; `close()` is
|
|
|
1593
1664
|
| GPIO and pins | `gpio(la, mode="output", *, level=None)`, `gpio_pins(las, mode="output", *, level=None)`, `configure_gpio(las, mode="output", *, level=None)`, `set_gpio(la, level)`, `read_gpio(la)`, `wait_for_level(la, level, *, timeout, poll=0.005)` (`False` when `timeout` passes first), `pin_levels()`, `la_pins()`, `release_gpio(*las)`, `la_step(la, *, steps, delay, dir_la=None, direction=0)` |
|
|
1594
1665
|
| I2C sensor | `enable_i2c_sensor(sensor=Sensor.BMP280, *, sda=None, scl=None, address=None, **values)`, `set_i2c_sensor(**values)`, `disable_i2c_sensor()`, `i2c_sensor_status()`, `i2c_sensor_types()`, `i2c_sensor_regs()`, `i2c_sensor_capture(...)` |
|
|
1595
1666
|
| GPS receiver | `enable_gps(tx, *, baud=9600, rate_hz=1, sentences=None, utc=None, **fix)`, `set_gps(*, utc=None, **fix)`, `disable_gps()`, `gps_status()` |
|
|
1596
|
-
| Analog | `analog_path(path)`, `adc_read(source="ext")`, `dac_output(path, *, volts=None)`, `current_out(current)`, `current_out_range()`, `calibrate(source="current_in")`, `calibration()`, `clear_calibration()` |
|
|
1667
|
+
| Analog | `analog_path(path, *, range=None)`, `adc_read(source="ext", *, range=None)`, `adc_range(range=None)`, `adc_range_test()`, `dac_output(path, *, volts=None)`, `current_out(current)`, `current_out_range()`, `calibrate(source="current_in")`, `calibration()`, `clear_calibration(part=None)`, `calibrate_dac(path, points)`, `calibrate_adc_gain(source, *, range="normal")`, `DMM_SET_POINTS` |
|
|
1597
1668
|
| Captures | `capture_adc(...)`, `capture_la(...)`, `capture_correlated(...)`, `decode(source, protocol="i2c", ...)` |
|
|
1598
1669
|
| DAC | `generate(...)`, `replay(...)`, `replay_waveform(...)`, `dac_stop()`, `control_loop(...)`, `loop_input(...)`, `loop_probe()` |
|
|
1599
1670
|
| CAN | `open_can(*, bitrate=500000, mode="normal", term=False, fd=False)`, `can_config(...)`, `can_disable()`, `can_status()`, `can_term(on)`, `can_write(...)`, `can_read(...)`, `can_respond(...)`, `can_respond_clear()` |
|
|
@@ -1634,12 +1705,16 @@ few built from other shapes); `to_dict()` goes the other way where a type is sen
|
|
|
1634
1705
|
| `I2CByte` | `I2CMessage.data` | `value`, `ack` |
|
|
1635
1706
|
| `UartFrame` | `decode("uart")` | `index`, `start_us`, `end_us`, `value`, `hex`, `text`, `ok`, `error` |
|
|
1636
1707
|
| `SpiFrame` | `decode("spi")` | `index`, `start_us`, `end_us`, `mosi`, `miso`, `mosi_hex`, `miso_hex` |
|
|
1637
|
-
| `AnalogPathState` | `analog_path` | `path`, `dac_mux_register`, `cal_relay_register` |
|
|
1638
|
-
| `AdcReading` | `adc_read` | `source`, `voltage`, `count`, `span`, `offset`, `current` |
|
|
1708
|
+
| `AnalogPathState` | `analog_path` | `path`, `dac_mux_register`, `cal_relay_register`, `range` |
|
|
1709
|
+
| `AdcReading` | `adc_read` | `source`, `voltage`, `count`, `span`, `offset`, `current`, `range`, `fullscale`, `overrange` |
|
|
1710
|
+
| `AdcRangeState` | `adc_range` | `range`, `supported`, `fullscale`, `normal_fullscale`, `low_fullscale`, `a`, `b` (volts, the `ext` fit of the range) |
|
|
1711
|
+
| `AdcRangeTest` | `adc_range_test` | `passed`, `points` (each an `AdcRangeTestPoint`: `set_voltage`, `normal`, `low`, `ok`) |
|
|
1712
|
+
| `AdcRange` (literal) | `adc_range`, `range=` | `"normal"`, `"low"` |
|
|
1639
1713
|
| `DacOutput` | `dac_output` | `path`, `voltage`, `code` |
|
|
1640
1714
|
| `CurrentOutput` | `current_out`, `current_out_range` | `current`, `code`, `min_current`, `max_current` |
|
|
1641
|
-
| `Calibration` | `calibrate`, `calibration`, `clear_calibration` | `source`, `calibrated`, `offset`, `a`, `b`, `count`, `span`, `samples` |
|
|
1642
|
-
| `
|
|
1715
|
+
| `Calibration` | `calibrate`, `calibration`, `clear_calibration`, `calibrate_dac`, `calibrate_adc_gain` | `source`, `calibrated`, `offset`, `a`, `b`, `count`, `span`, `samples`, `dac`, `adc_gain_ppm`, `adc_low_gain_ppm`, `dmm`, `measured`, `zero_count` |
|
|
1716
|
+
| `DacLine` | `Calibration.dac` | `a`, `b`, `offset`, `gain_ppm` |
|
|
1717
|
+
| `Capture` | `capture_adc` | `counts`, `volts`, `currents`, `sample_rate_hz`, `source`, `trigger`, `range`, `overrange`, `clipped_samples`, `duration`, `times()`, `mean()`, `min()`, `max()`, `rms()`, `rms_ac()`, `peak_to_peak()`, `first_crossing()`, `crossing_times()`, `fft()`, `dominant_frequency()` |
|
|
1643
1718
|
| `LaCapture` | `capture_la` | `words`, `channels`, `sample_rate_hz`, `trigger`, `duration`, `channel(la)`, `level_at()`, `edges()`, `edge_times()`, `first_edge()`, `delay()`, `frequency()`, `duty_cycle()`, `pulse_widths()`, `decode()` |
|
|
1644
1719
|
| `CorrelatedCapture` | `capture_correlated` | `adc` (a `Capture`), `la` (a `LaCapture`), sharing one hardware trigger |
|
|
1645
1720
|
| `Trigger` | `capture_adc`, `capture_la`, `capture_correlated` | `la`, `edge` |
|
|
@@ -1680,9 +1755,9 @@ reported keeps its default (`False`, `0` or `""`), so a feature check is just
|
|
|
1680
1755
|
| Group | Fields |
|
|
1681
1756
|
|---|---|
|
|
1682
1757
|
| Identity | `board`, `firmware_version`, `board_rev` (`"v2"`, `"v3"`, `"unknown"`, or `""`), `la_vccio_mv` (the LA bank voltage the pod reports) |
|
|
1683
|
-
| ADC | `adc_bits`, `adc_fullscale_mv`, `adc_channels`, `adc_offset_counts`, `adc_affine` (the front-end fit), `adc_max_count`, `counts_to_volts(count)` |
|
|
1758
|
+
| ADC | `adc_bits`, `adc_fullscale_mv`, `adc_channels`, `adc_offset_counts`, `adc_affine` (the front-end fit, of the range in use), `adc_low_range` (analog module v3r4+: the low range), `adc_range` (`"normal"`, `"low"`, or `""` before firmware 3.9.5), `adc_range_fullscale` (volts, ±), `adc_max_count`, `counts_to_volts(count)` |
|
|
1684
1759
|
| DAC and replay | `dac`, `dac_dc`, `dac_replay`, `dac_deep_replay`, `dac_control_loop`, `dac_loop_sources` (gateware v29+), `dac_loop_input_map` (v30+), `dac_cotrig` (v27+), `generate16` (firmware 3.9.1+: `generate` in calibrated 16-bit volts), `dac_bits`, `dac_replay_bits`, `dac_replay_max_samples`, `dac_fullscale_mv`, `dac_channels`, `dac_limits` (output limits on the DAC paths) |
|
|
1685
|
-
| Features | `scope`, `analyzer`, `serial`, `tunnel`, `command`, `ota`, `la_pins`, `gpio_read` (direct pin reads, v35+), `capture_trigger` (v35+), `power_profile`, `capture_b64` (faster ADC read-back, used automatically), `nrst_pin` (the dedicated reset pin, rev3), `usb_cc` (rev3), `spi_master` (v45+), `spi_stream`, `calibrate`, `current_out`, `can`, `pod_current` (`PowerStatus.pod`), `sensor_types` (the BME280/SHT4x/MPU-6050 models), `gps` (the GPS receiver, gateware v48+), `analog` (`False` on the digital-only board, `None` when not announced) |
|
|
1760
|
+
| Features | `scope`, `analyzer`, `serial`, `tunnel`, `command`, `ota`, `la_pins`, `gpio_read` (direct pin reads, v35+), `capture_trigger` (v35+), `power_profile`, `capture_b64` (faster ADC read-back, used automatically), `nrst_pin` (the dedicated reset pin, rev3), `usb_cc` (rev3), `spi_master` (v45+), `spi_stream`, `calibrate`, `cal_dmm` (firmware 3.9.4+: `calibrate_dac`, `calibrate_adc_gain`), `current_out`, `can`, `pod_current` (`PowerStatus.pod`), `sensor_types` (the BME280/SHT4x/MPU-6050 models), `gps` (the GPS receiver, gateware v48+), `analog` (`False` on the digital-only board, `None` when not announced) |
|
|
1686
1761
|
| Firmware updates and policies | `flash_kb` (MCU flash in KiB, 0 when not reported), `blob_slots`, `ota_sig`, `sig_policy` (`"audit"`, `"required"`, ...), `sig_keys`, `sig_policy_cmd`, `lan_policy` (`"open"`, `"locked"`, `"off"`), `lan_policy_cmd`, `tunnel_max_tier`, `lease_state` (the pod refuses LAN writes while a cloud job holds it), `cloud_ca`, `cloud_proxy`, `ws_auth_v2` (server only) |
|
|
1687
1762
|
| Boot health | `safe_mode`, `safe_reason`, `last_crash`, `reset_cause`, `boot_warning()` (a one-line warning after safe mode or a crash, else `None`) |
|
|
1688
1763
|
| Source | `raw` (the map it was parsed from) |
|
|
@@ -1723,7 +1798,7 @@ timeout=60.0, lease_id=None)`. Errors raise `ServerApiError` (`status`), and an
|
|
|
1723
1798
|
| `curve_output_at(curve, input_code)`, `input_percent_to_code(percent)` | evaluate a curve the way the gateware does; an input percentage as a code |
|
|
1724
1799
|
| `encode_curve_b64url(codes)` | 16-bit DAC codes as little-endian base64url, the wire form of a curve |
|
|
1725
1800
|
| `INTERNAL`, `EXTERNAL`, `PIN1` … `PIN14`, `BMP280_ADDR_PRIMARY`, `BMP280_ADDR_SECONDARY` | named constants (see [API conventions](#api-conventions-and-stability)) |
|
|
1726
|
-
| `DacPath`, `DacOutputPath`, `AnalogPath`, `AdcSource`, `CalibrateSource` (`"current_in"`), `LoopSource`, `Waveshape`, `ReplayMapping`, `DecodeProtocol`, `CanMode`, `FaultType`, `GpioMode`, `Edge`, `TriggerEdge` | the `Literal` string types |
|
|
1801
|
+
| `DacPath`, `DacOutputPath`, `AnalogPath`, `AdcSource`, `CalibrateSource` (`"current_in"`), `DacCalPath` (`"3v3"`, `"5v"`, `"12v"`), `AdcGainSource` (`"cal1"`, `"cal2"`, `"ext"`), `CalibrationPart` (`"offset"`, `"dac"`, `"adc"`), `LoopSource`, `Waveshape`, `ReplayMapping`, `DecodeProtocol`, `CanMode`, `FaultType`, `GpioMode`, `Edge`, `TriggerEdge` | the `Literal` string types |
|
|
1727
1802
|
| `decode`, `i2c`, `can`, `control_loop`, `motor_emulator`, `dsp` | submodules: off-device decoders, I2C helpers, CAN types, curve helpers, the emulator's registers, and `dsp`, a pure-Python mirror of the server's record-to-replay processing |
|
|
1728
1803
|
|
|
1729
1804
|
## Examples
|
|
@@ -48,8 +48,12 @@ from .constants import (
|
|
|
48
48
|
PIN12,
|
|
49
49
|
PIN13,
|
|
50
50
|
PIN14,
|
|
51
|
+
AdcRange,
|
|
51
52
|
AdcSource,
|
|
52
53
|
CalibrateSource,
|
|
54
|
+
CalibrationPart,
|
|
55
|
+
DacCalPath,
|
|
56
|
+
AdcGainSource,
|
|
53
57
|
AnalogPath,
|
|
54
58
|
CanMode,
|
|
55
59
|
DacOutputPath,
|
|
@@ -114,9 +118,13 @@ from .results import Capture, CorrelatedCapture, LaCapture, LanTls, Trigger
|
|
|
114
118
|
from .transport.tls import delete_pin as forget_lan_tls, trust_lan_tls
|
|
115
119
|
from .server_api import ServerApi, ServerApiError, ServerPermissionDeniedError
|
|
116
120
|
from .state import (
|
|
121
|
+
AdcRangeState,
|
|
122
|
+
AdcRangeTest,
|
|
123
|
+
AdcRangeTestPoint,
|
|
117
124
|
AdcReading,
|
|
118
125
|
AnalogPathState,
|
|
119
126
|
Calibration,
|
|
127
|
+
DacLine,
|
|
120
128
|
CurrentOutput,
|
|
121
129
|
DacOutput,
|
|
122
130
|
EfuseState,
|
|
@@ -159,8 +167,12 @@ __all__ = [
|
|
|
159
167
|
"PullState",
|
|
160
168
|
"AnalogPathState",
|
|
161
169
|
"DacOutput",
|
|
170
|
+
"AdcRangeState",
|
|
171
|
+
"AdcRangeTest",
|
|
172
|
+
"AdcRangeTestPoint",
|
|
162
173
|
"AdcReading",
|
|
163
174
|
"Calibration",
|
|
175
|
+
"DacLine",
|
|
164
176
|
"CurrentOutput",
|
|
165
177
|
"FpgaImageInfo",
|
|
166
178
|
"LoopState",
|
|
@@ -252,8 +264,12 @@ __all__ = [
|
|
|
252
264
|
"DacPath",
|
|
253
265
|
"DacOutputPath",
|
|
254
266
|
"AnalogPath",
|
|
267
|
+
"AdcRange",
|
|
255
268
|
"AdcSource",
|
|
256
269
|
"CalibrateSource",
|
|
270
|
+
"CalibrationPart",
|
|
271
|
+
"DacCalPath",
|
|
272
|
+
"AdcGainSource",
|
|
257
273
|
"Waveshape",
|
|
258
274
|
"ReplayMapping",
|
|
259
275
|
"LoopSource",
|
|
@@ -115,6 +115,13 @@ class Capabilities:
|
|
|
115
115
|
adc_offset_counts: float = 0.0
|
|
116
116
|
#: affine front-end fit; None => use the naive linear model.
|
|
117
117
|
adc_affine: Optional[AffineCal] = None
|
|
118
|
+
#: The board has the ADC low range (analog module v3r4+): :meth:`BenchPod.adc_range`.
|
|
119
|
+
adc_low_range: bool = False
|
|
120
|
+
#: The ADC input range :attr:`adc_affine` is for: ``"normal"`` or ``"low"``; ``""`` when the
|
|
121
|
+
#: firmware does not say (before 3.9.5: always normal).
|
|
122
|
+
adc_range: str = ""
|
|
123
|
+
#: That range's input full scale in volts (±), or 0.0 when not reported.
|
|
124
|
+
adc_range_fullscale: float = 0.0
|
|
118
125
|
|
|
119
126
|
# DAC / replay
|
|
120
127
|
dac: bool = False
|
|
@@ -176,6 +183,10 @@ class Capabilities:
|
|
|
176
183
|
spi_stream: bool = False
|
|
177
184
|
#: The pod can calibrate its own ADC and keeps the result: :meth:`BenchPod.calibrate`.
|
|
178
185
|
calibrate: bool = False
|
|
186
|
+
#: ``calibrate`` also takes the DMM-guided forms (firmware 3.9.4+): this pod's own DAC line
|
|
187
|
+
#: per output and ADC gain per source, :meth:`BenchPod.calibrate_dac`,
|
|
188
|
+
#: :meth:`BenchPod.calibrate_adc_gain`.
|
|
189
|
+
cal_dmm: bool = False
|
|
179
190
|
#: The pod can hold a current on its 4-20 mA output (J9): :meth:`BenchPod.current_out`.
|
|
180
191
|
current_out: bool = False
|
|
181
192
|
#: Classic CAN on the pod's CAN+/CAN- terminals: :meth:`BenchPod.open_can`. Firmware older
|
|
@@ -319,9 +330,11 @@ class Capabilities:
|
|
|
319
330
|
("spi_master", "spi_master"),
|
|
320
331
|
("spi_stream", "spi_stream"),
|
|
321
332
|
("calibrate", "calibrate"),
|
|
333
|
+
("cal_dmm", "cal_dmm"),
|
|
322
334
|
("current_out", "current_out"),
|
|
323
335
|
("can", "can"),
|
|
324
336
|
("pod_current", "pod_current"),
|
|
337
|
+
("adc_low_range", "adc_low_range"),
|
|
325
338
|
("dac_limits", "dac_limits"),
|
|
326
339
|
("sensor_types", "sensor_types"),
|
|
327
340
|
("gps", "gps"),
|
|
@@ -336,8 +349,19 @@ class Capabilities:
|
|
|
336
349
|
c.last_crash = _crash_text(status.get("last_crash", status.get("crash")))
|
|
337
350
|
c.reset_cause = str(status.get("reset", status.get("reset_cause", "")) or "")
|
|
338
351
|
c._apply_affine(status)
|
|
352
|
+
c._apply_range(status)
|
|
339
353
|
return c
|
|
340
354
|
|
|
355
|
+
def _apply_range(self, src: Mapping[str, Any], prefix: str = "") -> None:
|
|
356
|
+
"""The ADC input range the fit is for (firmware 3.9.5+)."""
|
|
357
|
+
rng = src.get(prefix + "adc_range")
|
|
358
|
+
if isinstance(rng, str) and rng:
|
|
359
|
+
self.adc_range = rng
|
|
360
|
+
if (v := _as_float(src, prefix + "adc_range_fs_mv")) is not None and v > 0:
|
|
361
|
+
self.adc_range_fullscale = v / 1000.0
|
|
362
|
+
if _as_bool(src, prefix + "adc_low_range") is True:
|
|
363
|
+
self.adc_low_range = True
|
|
364
|
+
|
|
341
365
|
@classmethod
|
|
342
366
|
def from_parameters(cls, params: Mapping[str, Any]) -> "Capabilities":
|
|
343
367
|
"""Parse a server ``cap.*`` device-parameter map (complete capability set)."""
|
|
@@ -379,8 +403,9 @@ class Capabilities:
|
|
|
379
403
|
("capture_b64", "cap.capture_b64"),
|
|
380
404
|
("nrst_pin", "cap.nrst_pin"), ("usb_cc", "cap.usb_cc"),
|
|
381
405
|
("spi_master", "cap.spi_master"), ("spi_stream", "cap.spi_stream"),
|
|
382
|
-
("calibrate", "cap.calibrate"), ("current_out", "cap.current_out"),
|
|
406
|
+
("calibrate", "cap.calibrate"), ("cal_dmm", "cap.cal_dmm"), ("current_out", "cap.current_out"),
|
|
383
407
|
("can", "cap.can"), ("pod_current", "cap.pod_current"),
|
|
408
|
+
("adc_low_range", "cap.adc_low_range"),
|
|
384
409
|
("sensor_types", "cap.sensor_types"), ("gps", "cap.gps"),
|
|
385
410
|
("dac_limits", "cap.dac_limits"),
|
|
386
411
|
("ws_auth_v2", "cap.ws_auth_v2"),
|
|
@@ -408,6 +433,7 @@ class Capabilities:
|
|
|
408
433
|
c.adc_affine = AffineCal(a=cal_a, b=cal_b, unwrap=bool(_as_bool(params, "cap.adc_cal_unwrap")))
|
|
409
434
|
else:
|
|
410
435
|
c._apply_affine(params, board=c.board)
|
|
436
|
+
c._apply_range(params, "cap.")
|
|
411
437
|
return c
|
|
412
438
|
|
|
413
439
|
def _apply_affine(self, src: Mapping[str, Any], board: Optional[str] = None) -> None:
|
|
@@ -147,7 +147,7 @@ def capture_adc(transport: Any, caps: Capabilities, *, samples: int = 4096,
|
|
|
147
147
|
counts.extend(_dense(chunk))
|
|
148
148
|
volts = [caps.counts_to_volts(c) for c in counts]
|
|
149
149
|
return Capture(counts=counts, volts=volts, sample_rate_hz=_rate_hz(rate, sample_rate_hz),
|
|
150
|
-
source=source)
|
|
150
|
+
source=source, range=caps.adc_range)
|
|
151
151
|
|
|
152
152
|
|
|
153
153
|
def capture_la(transport: Any, *, samples: int = 4096, sample_rate_hz: Optional[float] = None,
|
|
@@ -279,7 +279,8 @@ def capture_correlated(transport: Any, caps: Capabilities, *, adc_samples: int =
|
|
|
279
279
|
la_words = la_dense[:la_samples] if la_samples else la_dense
|
|
280
280
|
|
|
281
281
|
adc = Capture(counts=adc_counts, volts=[caps.counts_to_volts(c) for c in adc_counts],
|
|
282
|
-
sample_rate_hz=_rate_hz(adc_rate, adc_sample_rate_hz), trigger=trigger
|
|
282
|
+
sample_rate_hz=_rate_hz(adc_rate, adc_sample_rate_hz), trigger=trigger,
|
|
283
|
+
range=caps.adc_range)
|
|
283
284
|
la = LaCapture(words=la_words, sample_rate_hz=_rate_hz(la_rate, la_sample_rate_hz),
|
|
284
285
|
trigger=trigger)
|
|
285
286
|
return CorrelatedCapture(adc=adc, la=la)
|